Project Description

These samples are basically an addition to the one sample in the paper that was analysed previously. They are cells from patients with ovarian cancer, we isolated the tumour and stromal cells for each patient. And have mixed them back together to give 1 sample per patient.

So the only difference from the previous sample is that these have the tumour and stromals from the same patient mixed together in a 75%:25% ratio

Aim

The aims were

1. To cluster them establish the 2 populations
2. To establish the over-dispersed genes in the two populations

From the first sample p53 and XIST were differentially expressed so you could use these to validate the initial clustering. The analysis after that would be the same as the previous sample

Data preperation

Loading library

library(Seurat)
library(scater)
library(ggplot2)
library(scran)
library(GGally)
library(mclust)
library(Rtsne)
library(viridis)
#library(umapr)
library(umap)
library(tidyverse)
library(paletteer)

Setting up color palettes. I was using paletter package as it was giving a colour blind palette. But now I am using the palette that Matt gave from Conor. This gives a useful clustering for Matt

Setting up colorblind friendly color palette:

#cbPalette <- paletteer_d(package = "ggthemes", palette="calc", n=12)
 c30 <- c("dodgerblue2",#1
         "#E31A1C", #2 red
          "green4", #3
         "#FF7F00", #4 orange
           "green1",#5
         "purple",#6
         "blue1",#7
         "deeppink1",#8
         "darkorange4",#9
          "black",#10
         "gold1",#11
         "darkturquoise",#12
         "#6A3D9A", #13 purple
         "orchid1",#14
         "gray70",#15
          "maroon",#16
         "palegreen2",#17
          "#333333",#18
          "#CAB2D6", #19 lt purple
          "#FDBF6F", #20 lt orange
         "khaki2",#21
         "skyblue2",#22
         "steelblue4",#23
         "green1",#24
         "yellow4",#25
         "yellow3",#26
         "#FB9A99", #27 lt pink
         "brown",#28
         "#000099",#29
         "#CC3300"#30
         )
 
pie(rep(1,30), col=c30)

Choosing for the samples

c_sample_col <- c30[c(3,22,19,30)]

Choosing for the clusters

c_clust_col <- c30[c(1,2,3,4,5,6,7,8,11,14,22,24)]

__ This data set was not normalized using 10X downsampling as the samples have large variability in their cell numbers.

Importing cell-ranger data

cellranger_pipestance_path <- "/home/ubuntu/Rna-seq_Data-Analysis/Louisa_Nelson_10X_Analysis/LN_aggr/outs/filtered_feature_bc_matrix/"
ovarianCancer <- Read10X(cellranger_pipestance_path)
#analysis_results <- load_cellranger_analysis_results(cellranger_pipestance_path)
ovarianCancer
33538 x 20717 sparse Matrix of class "dgCMatrix"
   [[ suppressing 73 column names ‘AAACCCACAGTTAGGG-1’, ‘AAACCCACATGTGTCA-1’, ‘AAACCCAGTCGCATGC-1’ ... ]]
   [[ suppressing 73 column names ‘AAACCCACAGTTAGGG-1’, ‘AAACCCACATGTGTCA-1’, ‘AAACCCAGTCGCATGC-1’ ... ]]
                                                                                                                                                                    
MIR1302-2HG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
FAM138A     . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
OR4F5       . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AL627309.1  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AL627309.3  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AL627309.2  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AL627309.4  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......

 ..............................
 ........suppressing 20644 columns and 33525 rows in show(); maybe adjust 'options(max.print= *, width = *)'
 ..............................
   [[ suppressing 73 column names ‘AAACCCACAGTTAGGG-1’, ‘AAACCCACATGTGTCA-1’, ‘AAACCCAGTCGCATGC-1’ ... ]]
                                                                                                                                                                   
AC004556.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AC233755.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AC233755.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
AC240274.1 1 . . . . . . . . . . . 1 . . . . . . . . . . . 1 . . . . 1 . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . ......
AC213203.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......
FAM231C    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ......

Extracting cell information

Sample1_barcodes  <- data.frame(barcode=colnames(ovarianCancer)[grep("1", colnames(ovarianCancer))], Sample="Patient1")
Sample2_barcodes  <- data.frame(barcode=colnames(ovarianCancer)[grep("2", colnames(ovarianCancer))], Sample="Patient2")
Sample3_barcodes  <- data.frame(barcode=colnames(ovarianCancer)[grep("3", colnames(ovarianCancer))], Sample="Patient3")
Sample4_barcodes  <- data.frame(barcode=colnames(ovarianCancer)[grep("4", colnames(ovarianCancer))], Sample="Patient4")
print(paste0('Number of Sample1 cells: ',dim(Sample1_barcodes)[1]))
[1] "Number of Sample1 cells: 5756"
print(paste0('Number of Sample2 cells: ',dim(Sample2_barcodes)[1]))
[1] "Number of Sample2 cells: 3974"
print(paste0('Number of Sample3 cells: ',dim(Sample3_barcodes)[1]))
[1] "Number of Sample3 cells: 6433"
print(paste0('Number of Sample4 cells: ',dim(Sample4_barcodes)[1]))
[1] "Number of Sample4 cells: 4554"

Confirmed this number with the QC output produced by cell ranger.

annotBarcode <- rbind(Sample1_barcodes, Sample2_barcodes, Sample3_barcodes, Sample4_barcodes)
rownames(annotBarcode) <- annotBarcode$barcode
head(annotBarcode)

Creating the SingleCellExperiment object using scater. We would be using the exprs function of gbm. Later on scater might add its own function to input 10X data, but before that we will be using this method

cdSc <- SingleCellExperiment(assays = list(counts = as.matrix(ovarianCancer)))
colData(cdSc)$barcode <- annotBarcode$barcode
colData(cdSc)$Sample <- annotBarcode$Sample
rowData(cdSc)$symbol <- rownames(ovarianCancer)
cdSc <- scater::calculateQCMetrics(cdSc)
cdSc
class: SingleCellExperiment 
dim: 33538 20717 
metadata(0):
assays(1): counts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(8): symbol is_feature_control ... total_counts log10_total_counts
colnames(20717): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(11): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Here we use log2-counts-per-million with an offset of 1 as the exprs values. We have to note that the CPM for scater is different than the regular CPM calculation as we are considering the step-size here.

The value of the log-CPMs is explained by adding a prior count to avoid undefined values after the log-transformation, multiplying by a million, and dividing by the mean library size. This size factors are used to define the effective library sizes. This is done by scaling all size factors such that the mean scaled size factor is equal to the mean sum of counts across all features. The effective library sizes are then used to in the denominator of the CPM calculation. The way that scater calculates the log-normalized counts are

lib.sizes <- colSums(counts(example_sceset))
lib.sizes <- lib.sizes/mean(lib.sizes)
log2(counts(example_sceset)[1,]/lib.sizes+1)

We now normalize for all the counts

exprs(cdSc) <- log2(scater::calculateCPM(cdSc, use_size_factors = TRUE) + 1)

Saving Dataset

save.image()
# save.image('PC_20181002.RData')

QC analysis

At the start of the QC analysis we make different plots to visualize the summary statistics.

Cell QC

Low-quality cells need to be identified and removed to ensure that the technical effects do not distort downstream analysis results. Two common measures of cell quality are the library size and the number of expressed genes in each library. The library size is defined as the total sum of counts across all genes. Cells with relatively small library sizes are considered to be of low quality as the RNA has not been efficiently captured (i.e., converted into cDNA and amplified) during library preparation. The number of expressed genes in each cell is defined as the number of genes with non-zero counts for that cell. Any cell with very few expressed genes is likely to be of poor quality as the diverse transcript population has not been successfully captured.

Reads mapping with mitochondrial reads

We identify the rows corresponding to mitochondrial genes. Another important measure of quality is the proportion of reads mapped to genes in the mitochondrial genome. High proportions are indicative of poor-quality cells (Ilicic et al., 2016; Islam et al., 2014), possibly because of increased apoptosis and/or loss of cytoplasmic RNA from lysed cells.

Reads mapping with mitochondrial reads

We identify the rows corresponding to mitochondrial genes.

library(biomaRt)
my.ids <- gsub('\\..*','',rownames(cdSc))
ensembl = useEnsembl(biomart="ensembl", dataset="hsapiens_gene_ensembl")
chrName <- getBM(attributes=c('ensembl_gene_id','hgnc_symbol','chromosome_name'), filters = 'hgnc_symbol', values =my.ids, mart = ensembl)

Batch submitting query [==>--------------------------------------------------------------------------------------------------------------]   3% eta: 10s
Batch submitting query [====>------------------------------------------------------------------------------------------------------------]   4% eta: 12s
Batch submitting query [======>----------------------------------------------------------------------------------------------------------]   6% eta: 13s
Batch submitting query [=======>---------------------------------------------------------------------------------------------------------]   7% eta: 13s
Batch submitting query [=========>-------------------------------------------------------------------------------------------------------]   9% eta: 12s
Batch submitting query [===========>-----------------------------------------------------------------------------------------------------]  10% eta: 13s
Batch submitting query [============>----------------------------------------------------------------------------------------------------]  12% eta: 13s
Batch submitting query [==============>--------------------------------------------------------------------------------------------------]  13% eta: 13s
Batch submitting query [================>------------------------------------------------------------------------------------------------]  15% eta: 13s
Batch submitting query [=================>-----------------------------------------------------------------------------------------------]  16% eta: 12s
Batch submitting query [===================>---------------------------------------------------------------------------------------------]  18% eta:  1m
Batch submitting query [=====================>-------------------------------------------------------------------------------------------]  19% eta:  1m
Batch submitting query [======================>------------------------------------------------------------------------------------------]  21% eta: 50s
Batch submitting query [========================>----------------------------------------------------------------------------------------]  22% eta: 46s
Batch submitting query [==========================>--------------------------------------------------------------------------------------]  24% eta: 43s
Batch submitting query [===========================>-------------------------------------------------------------------------------------]  25% eta: 41s
Batch submitting query [=============================>-----------------------------------------------------------------------------------]  26% eta: 43s
Batch submitting query [===============================>---------------------------------------------------------------------------------]  28% eta: 40s
Batch submitting query [================================>--------------------------------------------------------------------------------]  29% eta: 38s
Batch submitting query [==================================>------------------------------------------------------------------------------]  31% eta: 36s
Batch submitting query [====================================>----------------------------------------------------------------------------]  32% eta: 34s
Batch submitting query [=====================================>---------------------------------------------------------------------------]  34% eta: 32s
Batch submitting query [=======================================>-------------------------------------------------------------------------]  35% eta: 32s
Batch submitting query [=========================================>-----------------------------------------------------------------------]  37% eta: 31s
Batch submitting query [==========================================>----------------------------------------------------------------------]  38% eta: 30s
Batch submitting query [============================================>--------------------------------------------------------------------]  40% eta: 37s
Batch submitting query [==============================================>------------------------------------------------------------------]  41% eta: 35s
Batch submitting query [===============================================>-----------------------------------------------------------------]  43% eta: 37s
Batch submitting query [=================================================>---------------------------------------------------------------]  44% eta: 35s
Batch submitting query [===================================================>-------------------------------------------------------------]  46% eta: 33s
Batch submitting query [====================================================>------------------------------------------------------------]  47% eta: 32s
Batch submitting query [======================================================>----------------------------------------------------------]  49% eta: 33s
Batch submitting query [=======================================================>---------------------------------------------------------]  50% eta: 32s
Batch submitting query [=========================================================>-------------------------------------------------------]  51% eta: 34s
Batch submitting query [===========================================================>-----------------------------------------------------]  53% eta: 33s
Batch submitting query [============================================================>----------------------------------------------------]  54% eta: 31s
Batch submitting query [==============================================================>--------------------------------------------------]  56% eta: 31s
Batch submitting query [================================================================>------------------------------------------------]  57% eta: 30s
Batch submitting query [=================================================================>-----------------------------------------------]  59% eta: 28s
Batch submitting query [===================================================================>---------------------------------------------]  60% eta: 27s
Batch submitting query [=====================================================================>-------------------------------------------]  62% eta: 25s
Batch submitting query [======================================================================>------------------------------------------]  63% eta: 24s
Batch submitting query [========================================================================>----------------------------------------]  65% eta: 22s
Batch submitting query [==========================================================================>--------------------------------------]  66% eta: 21s
Batch submitting query [===========================================================================>-------------------------------------]  68% eta: 20s
Batch submitting query [=============================================================================>-----------------------------------]  69% eta: 19s
Batch submitting query [===============================================================================>---------------------------------]  71% eta: 19s
Batch submitting query [================================================================================>--------------------------------]  72% eta: 18s
Batch submitting query [==================================================================================>------------------------------]  74% eta: 17s
Batch submitting query [====================================================================================>----------------------------]  75% eta: 15s
Batch submitting query [=====================================================================================>---------------------------]  76% eta: 15s
Batch submitting query [=======================================================================================>-------------------------]  78% eta: 15s
Batch submitting query [=========================================================================================>-----------------------]  79% eta: 13s
Batch submitting query [==========================================================================================>----------------------]  81% eta: 12s
Batch submitting query [============================================================================================>--------------------]  82% eta: 11s
Batch submitting query [==============================================================================================>------------------]  84% eta: 10s
Batch submitting query [===============================================================================================>-----------------]  85% eta:  9s
Batch submitting query [=================================================================================================>---------------]  87% eta:  8s
Batch submitting query [===================================================================================================>-------------]  88% eta:  7s
Batch submitting query [====================================================================================================>------------]  90% eta:  6s
Batch submitting query [======================================================================================================>----------]  91% eta:  5s
Batch submitting query [========================================================================================================>--------]  93% eta:  4s
Batch submitting query [=========================================================================================================>-------]  94% eta:  3s
Batch submitting query [===========================================================================================================>-----]  96% eta:  3s
Batch submitting query [=============================================================================================================>---]  97% eta:  2s
Batch submitting query [==============================================================================================================>--]  99% eta:  1s
Batch submitting query [=================================================================================================================] 100% eta:  0s
                                                                                                                                                        
chrName <- chrName[match(my.ids, chrName$hgnc_symbol),]
is.mito <- chrName$chromosome_name == "MT" & !is.na(chrName$chromosome_name)
sum(is.mito)
[1] 13
cdSc <- calculateQCMetrics(cdSc, feature_controls=list(Mt=is.mito))

Plotting the histograms of my QC entries.

par(mfrow=c(2,2))
hist((cdSc$total_counts)/1e6, xlab="Library sizes (millions)", main="Histogram of Library size",
     breaks=100, col="grey80", ylab="Number of cells")
hist(cdSc$total_features_by_counts, xlab="Number of expressed genes", main="Histogram of No. of Features",
     breaks=100, col="grey80", ylab="Number of cells")
hist(cdSc$pct_counts_Mt, xlab="Mitochondrial proportion (%)",
     ylab="Number of cells", breaks=100, main="Histogram of Mictochondria %", col="grey80")

The summary statistics for mitochondrial read proportion is reasonably good. As anything below 10% is very good and here the mean is around 7%. However, there are some outlier cells expressing very high mitocondrial genes. I am going to filter those genes out.

summary(cdSc$pct_counts_Mt)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   4.498   6.376   6.939   8.246  94.326 

It is also valuable to examine how the QC metrics behave with respect to each other. Generally, they will be in rough agreement, i.e., cells with low total counts will also have low numbers of expressed features and high mitochondrial proportions.

par(mfrow=c(1,3))
plot(cdSc$total_features_by_counts, cdSc$total_counts/1e6, xlab="Number of expressed genes",
    ylab="Library size (millions)")
plot(cdSc$total_features_by_counts, cdSc$pct_counts_Mt, xlab="Number of expressed genes",
    ylab="Mitochondrial proportion (%)")
plot(cdSc$total_counts/1e6, cdSc$pct_counts_Mt, xlab="Total counts (in millions)",
    ylab="Mitochondrial proportion (%)")

Also how the counts are distributed across the cells..

plotRowData(cdSc, x = "n_cells_by_counts", y = "log10_total_counts", colour_by = "is_feature_control_Mt")

In the figure above we see that some of the mitochondrial genes (marked in orange) at the top right corner of the figure has very high log10_total_counts and also are expressed in a large number of cells (almost in all cells). This is not very surprising as some of the mitochondrial genes are indeed expressed across all the cells as they are producing energy. The worrying part would be if these genes takes majority of a cell’s read (more than 80%). Then I would remove those cells.

plotRowData(cdSc, x = "n_cells_by_counts", y = "log10_total_counts", colour_by = "pct_dropout_by_counts")

As expected, cells with low number of reads have higher dropout counts.

plotColData(cdSc, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

Again cells with lower read counts and lower total feature counts have very high proportion of mitchondrial reads, clearly indicating that these cells either started apoptosis or broken abruptly. We can safely remove these cells in our QC filtering.

To see the QC metrics related with cells,

names(colData(cdSc))
 [1] "barcode"                                        "Sample"                                         "is_cell_control"                               
 [4] "total_features_by_counts"                       "log10_total_features_by_counts"                 "total_counts"                                  
 [7] "log10_total_counts"                             "pct_counts_in_top_50_features"                  "pct_counts_in_top_100_features"                
[10] "pct_counts_in_top_200_features"                 "pct_counts_in_top_500_features"                 "total_features_by_counts_endogenous"           
[13] "log10_total_features_by_counts_endogenous"      "total_counts_endogenous"                        "log10_total_counts_endogenous"                 
[16] "pct_counts_endogenous"                          "pct_counts_in_top_50_features_endogenous"       "pct_counts_in_top_100_features_endogenous"     
[19] "pct_counts_in_top_200_features_endogenous"      "pct_counts_in_top_500_features_endogenous"      "total_features_by_counts_feature_control"      
[22] "log10_total_features_by_counts_feature_control" "total_counts_feature_control"                   "log10_total_counts_feature_control"            
[25] "pct_counts_feature_control"                     "pct_counts_in_top_50_features_feature_control"  "pct_counts_in_top_100_features_feature_control"
[28] "pct_counts_in_top_200_features_feature_control" "pct_counts_in_top_500_features_feature_control" "total_features_by_counts_Mt"                   
[31] "log10_total_features_by_counts_Mt"              "total_counts_Mt"                                "log10_total_counts_Mt"                         
[34] "pct_counts_Mt"                                  "pct_counts_in_top_50_features_Mt"               "pct_counts_in_top_100_features_Mt"             
[37] "pct_counts_in_top_200_features_Mt"              "pct_counts_in_top_500_features_Mt"             

and to see the QC metrics related with genes,

names(rowData(cdSc))
[1] "symbol"                "is_feature_control"    "is_feature_control_Mt" "mean_counts"           "log10_mean_counts"     "n_cells_by_counts"    
[7] "pct_dropout_by_counts" "total_counts"          "log10_total_counts"   
plotColData(cdSc, x = "log10_total_features_by_counts", y="log10_total_counts", size_by ="total_counts_Mt", colour_by  = "pct_counts_Mt")

All the above figures clearly show that the high proportions of reads mapped to genes in the mitochondrial genome is indicative of poor quality cells (Ilicic et al., 2016; Islam et al., 2014), possibly because of increased apoptosis and/or loss of cytoplasmic RNA from lysed cells. I would be filtering those cells out.

The following figure gives an idea of how many genes are expressed in how many cells.

plotExprsFreqVsMean(cdSc)

The genes looks good as they are not yet filtered.

Now, picking up a threshold for different metrices in cell filtering is not straightforward as their absolute values depend on the protocol and biological system. For example, sequencing to greater depth will lead to more reads, regardless of the quality of the cells. To obtain an adaptive threshold, we assume that most of the dataset consists of high-quality cells. To facilitate in picking a threshold for our cell cutoff we now plot couple of densitiies.

cut_off_reads <- median(cdSc$log10_total_counts) - 3*mad(cdSc$log10_total_counts)
df <- data.frame(x=cdSc$log10_total_counts, Sample = cdSc$Sample)
plot_reads <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_reads, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Library Size)'), title = "Total reads density", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
cut_off_mRNA <- median(cdSc$log10_total_features_by_counts) - 3*mad(cdSc$log10_total_features_by_counts)
df <- data.frame(x=cdSc$log10_total_features_by_counts, Sample = cdSc$Sample)
plot_mRNA <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_mRNA, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Number of expressed genes)'), title = "Total genes expressed", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
cut_off_MT <- median(cdSc$pct_counts_Mt) + 3*mad(cdSc$pct_counts_Mt)
df <- data.frame(x=cdSc$pct_counts_Mt, Sample = cdSc$Sample)
plot_MT <- ggplot(df,
       aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_MT, colour="grey", linetype = "longdash") +
       labs(x = expression('Pct of MT Expression'), title = "Mitochondrial Expression", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col)
multiplot(plot_reads, plot_MT, plot_mRNA,  cols=2)

df <- data.frame(x=cdSc$log10_total_counts)
as_tibble(df) %>%
  dplyr::mutate("Sample" = cdSc$Sample) %>%
  ggplot( aes(x = x, fill = as.factor(Sample))) + 
       geom_density(alpha = 0.5) +
       geom_vline(xintercept = cut_off_reads, colour="grey", linetype = "longdash") +
       labs(x = expression('log'[10]*'(Library Size)'), title = "Total reads density", fill = "Sample") + 
       theme_classic(base_size = 14)+
       scale_fill_manual(values=c_sample_col) + facet_wrap(~Sample, nrow=2,ncol=2)

The distribution of the reads, number of expressed genes and the percent of MT expression. The shaded line represents the threshold which is the 3 Median Absolute Deviation (MAD). For the top two figures, cells on the left of this threshold would be filtered out and for the bottom figure, cells on the right of this figure would be filtered out.

We see that in the library size plot and also the plot with number of expressed features, there are a biomodal distribution. In the previous cellranger versions we generally do not observe this. But with new cellranger it can capture cells with lower RNA contents. Due to this we are now getting cells with lower read counts.

We remove cells with log-library sizes that are more than 4 median absolute deviations (MADs) below the median log-library size. (A log-transformation improves resolution at small values, especially when the MAD of the raw values is comparable to or greater than the median). We also remove cells where the log-transformed number of expressed genes is 4 MADs below the median. Similarly, we drop the cells having pct of MT expression below 3 MAD.

Although from the cellranger output it looked that the cells have very different total counts, but after removing the extreme outlier cells it looks like they have quite nice packed distribution.

However, in the G samples higher number of cells have higher read counts as can be seen from the individual density. But that does not impact the cutoff value. Below we check the cutoff values to validate our filtering.

print(paste0('Read count Cutoff: ',10^cut_off_reads))
[1] "Read count Cutoff: 1289.0232114447"
print(paste0('Genes count Cutoff: ',10^cut_off_mRNA))
[1] "Genes count Cutoff: 864.220903908248"
print(paste0('MT percent count Cutoff: ',cut_off_MT))
[1] "MT percent count Cutoff: 14.7140899519496"

We now calculate the cells and genes that are deemed to be outliers due to falling below 3 MAD.

Cells having read counts below 1289 (ByLibSize) and number of genes expressed below 864 (ByFeature) would be dropped and if cells are having more than 14% of mitochondrial reads, I would drop the cells.

libsize.drop <- isOutlier(cdSc$total_counts, nmads=3, type="lower", log=TRUE)
feature.drop <- isOutlier(cdSc$total_features_by_counts, nmads=3, type="lower", log=TRUE)
mito.drop <- isOutlier(cdSc$pct_counts_Mt, nmads=3, type="higher")

Below are the stats for the cells that would be dropped

data.frame(ByLibSize=sum(libsize.drop), ByFeature=sum(feature.drop), 
           ByMito=sum(mito.drop))

So, based on library size, 825 cells would be dropped and based on number of features expressed, 1210 cells would be dropped and for Mitochondrial proportion of reads 517 cells would be dropped. Please note that all these cells are removed from the total population and also many of these cells would be overlapping across conditions.

Generating, the scater object with filtered profile.

cdScFilt <- cdSc[,!(libsize.drop | feature.drop | mito.drop)]
cdScFilt <- calculateQCMetrics(cdScFilt)

Before filtering:

cdSc
class: SingleCellExperiment 
dim: 33538 20717 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control ... total_counts log10_total_counts
colnames(20717): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(38): barcode Sample ... pct_counts_in_top_200_features_Mt pct_counts_in_top_500_features_Mt
reducedDimNames(0):
spikeNames(0):

After filtering:

cdScFilt
class: SingleCellExperiment 
dim: 33538 19248 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(19248): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(38): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Before filtering number of cells were,

print(paste0("Cells before filtering: ",dim(cdSc)[2]))
[1] "Cells before filtering: 20717"

After filtering remaining cells are:

print(paste0("Cells remaining after filtering: ",dim(cdScFilt)[2]))
[1] "Cells remaining after filtering: 19248"

For the four samples, number of cells that are remaining are:

print(levels(as.factor(colData(cdSc)$Sample)))
[1] "Patient1" "Patient2" "Patient3" "Patient4"
print(paste0('Before Filtering: ', table(colData(cdSc)$Sample)))
[1] "Before Filtering: 5756" "Before Filtering: 3974" "Before Filtering: 6433" "Before Filtering: 4554"
print(paste0('After Filtering: ', table(colData(cdScFilt)$Sample)))
[1] "After Filtering: 5317" "After Filtering: 3804" "After Filtering: 6084" "After Filtering: 4043"

So, the cells that are being filtered out are kind of evenly distributed across the samples. This is kind of reassuring that there was not a point failure where one sample totally failed. After filtering we have in total 21609 cells remaining for downstream analysis which is a very reasonable number.

Now, I will again run couple of QCs to see whether further filtering is required.

Relationship between total counts and total features before filtering:

p <- plotColData(cdSc, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample") +
       scale_fill_manual(values=c_sample_col)
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

After filtering:

p <- p <- plotColData(cdScFilt, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample") +
       scale_fill_manual(values=c_sample_col)
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

We could see that some of the outlier cells have been dropped.

Before filtering:

plotColData(cdSc, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

After filtering:

plotColData(cdScFilt, x = "log10_total_counts", y = "log10_total_features_by_counts", colour_by = "pct_counts_Mt")

We see that the good cells are kept while cells with very high Mt reads are being removed.

Now to see about the higher read depth cells I plot violin plots. This would give me an indication as whether there is any obvious outliers. I will be plotting the cells for TotalCounts and TotalFeaturesMitochondria.

df <- data.frame(Cell=colnames(cdScFilt), CellType=cdScFilt$Sample, totalFeatures=cdScFilt$total_features_by_counts, totalCount=cdScFilt$total_counts, PctTotalCountMt=cdScFilt$pct_counts_Mt, Sample=cdScFilt$Sample)
p1 <- ggplot(df, aes(factor(CellType),totalCount,colour=Sample))
p1 <- p1 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p2 <- ggplot(df, aes(factor(CellType),PctTotalCountMt,colour=Sample))
p2 <- p2 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p3 <- ggplot(df, aes(factor(CellType),totalFeatures,colour=Sample))
p3 <- p3 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
multiplot(p1,p2,p3,cols = 2)

It clearly looks like that two cells above total counts of 30000 are outliers. This again would be removed as it would otherwise might be multiplets

cdScFilt
class: SingleCellExperiment 
dim: 33538 19248 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(19248): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(38): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):
cdScFilt <- cdScFilt[,!(cdScFilt$total_counts > 30000)]
cdScFilt
class: SingleCellExperiment 
dim: 33538 19246 
metadata(0):
assays(2): counts logcounts
rownames(33538): MIR1302-2HG FAM138A ... AC213203.1 FAM231C
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(19246): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(38): barcode Sample ... pct_counts_in_top_200_features pct_counts_in_top_500_features
reducedDimNames(0):
spikeNames(0):

Drawing the vuiolin plot again

df <- data.frame(Cell=colnames(cdScFilt), CellType=cdScFilt$Sample, totalFeatures=cdScFilt$total_features_by_counts, totalCount=cdScFilt$total_counts, PctTotalCountMt=cdScFilt$pct_counts_Mt, Sample=cdScFilt$Sample)
p1 <- ggplot(df, aes(factor(CellType),totalCount,colour=Sample))
p1 <- p1 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p2 <- ggplot(df, aes(factor(CellType),PctTotalCountMt,colour=Sample))
p2 <- p2 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
p3 <- ggplot(df, aes(factor(CellType),totalFeatures,colour=Sample))
p3 <- p3 + geom_violin() + geom_jitter(height = 0, width = 0.1) + theme_classic(base_size = 14) + scale_colour_manual(values=c_sample_col) +
  theme(axis.text.x=element_text(angle=90, hjust=1))
multiplot(p1,p2,p3,cols = 2)

Looks a good distribution to move forward.

save.image()

Classification of cell cycle phase

We use the prediction method described by Scialdone et al. (2015) to classify cells into cell cycle phases based on the gene expression data. Using a training dataset, the sign of the difference in expression between two genes was computed for each pair of genes. Pairs with changes in the sign across cell cycle phases were chosen as markers. Cells in a test dataset can then be classified into the appropriate phase, based on whether the observed sign for each marker pair is consistent with one phase or another. We do the cell cycle classification before gene filtering as this provides more precise cell cycle phase classifications. This approach is implemented in the Cyclone function using a pre-trained set of marker pairs for human data. Some additional work is necessary to match the gene symbols in the data to the Ensembl annotation in the pre-trained marker set.

cdScFiltAnnot <- cdScFilt
hg.pairs <- readRDS(system.file("exdata", "human_cycle_markers.rds", package="scran"))
library(org.Hs.eg.db)
Loading required package: AnnotationDbi

Attaching package: ‘AnnotationDbi’

The following object is masked from ‘package:dplyr’:

    select
anno <- select(org.Hs.eg.db, keys=as.character(rownames(cdScFiltAnnot)), keytype="SYMBOL", column="ENSEMBL")
'select()' returned 1:many mapping between keys and columns
ensembl <- anno$ENSEMBL[match(as.character(rownames(cdScFiltAnnot)), anno$SYMBOL)]
assignments <- cyclone(cdScFiltAnnot, hg.pairs, gene.names=ensembl)
df <- data.frame(x=assignments$score$G1, y=assignments$score$G2M, Sample=colData(cdScFilt)$Sample)
 p<-ggplot(data=df, aes(x=x,y=y,color=Sample)) + 
    geom_point(size=0.5)+
    xlab("G1 score")+
    ylab("G2M score")+
    ylim(0,1)+
    xlim(0,1)+
    ggtitle("Cell-cycle effects")+
    theme_light(base_size=15)+
    theme(axis.title.x = element_text(size=10, vjust=-2),
          axis.text.x  = element_text( size=10),
          axis.title.y = element_text( size=10,vjust=2),
          axis.text.y  = element_text( size=10)) +
    theme(plot.margin=unit(c(1,1,1.5,1.2),"cm"))+
    theme(legend.text=element_text(size=10),#size of legend
          legend.title=element_text(size=10), 
          plot.title = element_text(size=20, face="bold")) +
    scale_colour_manual(values=c_sample_col) + 
    #scale_color_discrete(name=legend.title)+
    geom_segment(aes(x = 1/2, y = 0, xend=1/2, yend=1/2),colour="black") + 
    geom_segment(aes(x = 0, y = 1/2, xend=1/2, yend=1/2),colour="black") +
    geom_segment(aes(x = 1/2, y = 1/2, xend=1, yend=1),colour="black") +
    annotate("text", x=0.05, y=0.05, label="S", size=8)+
    annotate("text", x=0.95, y=0.25, label="G1", size=8)+
    annotate("text", x=0.25, y=0.95, label="G2M", size=8)
print(p)

smoothScatter(assignments$score$G1, assignments$score$G2M, xlab="G1 score", ylab="G2/M score", pch=16, cex=0.6)

Cells are classified as being in G1 phase if the G1 score is above 0.5 and greater than the G2/M score; in G2/M phase if the G2/M score is above 0.5 and greater than the G1 score; and in S phase if neither score is above 0.5. Here, the vast majority of cells are classified as being in G1 phase.

It looks like majority of the cells are in high G2M or S score indicating that the cells are INDEED going through cell-cycle stages. This is we would also expect from Cancer cells.

This method would be less accurate for data that are substantially different from those used in the training set, e.g., due to the use of a different protocol. This dataset uses UMI counts, which has an entirely different set of biases, e.g., 3’-end coverage only, no length bias, no amplification noise. These new biases (and the absence of expected biases) may interfere with accurate classification of some cells. So we are not particularly sure about this result. Nevertheless we need to keep in mind that there could be quite high cell-cycle effect which might confound the dataset. To avoid problems from misclassification, I will not perform any processing of this dataset by cell cycle phase. This is unlikely to be problematic for this analysis, as the cell cycle effect will be relatively subtle compared to the obvious differences between cell types in a diverse population. Thus, the former is unlikely to distort the conclusions regarding the latter.

colData(cdScFilt)$CellCycle <- assignments$phases
colData(cdScFiltAnnot)$CellCycle <- assignments$phases
plotPCA(cdScFiltAnnot, colour_by = "CellCycle", shape_by="Sample")

It does not look like one patient is dominated by a cell-cycle phase.

Filtering out low-abundance genes

Low-abundance genes are problematic as zero or near-zero counts do not contain enough information for reliable statistical inference (Bourgon et al., 2010). In addition, the discreteness of the counts may interfere with downstream statistical procedures, e.g., by compromising the accuracy of continuous approximations.

Generally for low-abundance genes are defined as those with an average count below a filter threshold of 1. But 10X Chromium is based on UMI counts, which would have understandably low counts, so setting the threshold to 1 would filter a large number of cells. Here I am going to set it to 0.01. The figure below will explain the reason.

These genes are likely to be dominated by drop-out events (Brennecke et al., 2013), which limits their usefulness in later analyses. Removal of these genes mitigates discreteness and reduces the amount of computational work without major loss of information.

ave.counts <- rowMeans(counts(cdScFilt))
keep <- ave.counts >= 0.01
sum(keep)
[1] 13210

After filtering with average count of 0.01 there are 13210 genes left for the downsteram analysis.

To check whether the chosen threshold is suitable, we examine the distribution of log-means across all genes (Figure below). Generally for higher number of cells there is a peak on the right hand side that represents the bulk of moderately expressed genes while in the middle there is a rectangular component that corresponds to lowly expressed genes. The filter threshold should cut the distribution at some point along the rectangular component to remove the majority of low-abundance genes. As the blue line repsresents in the figure below, it cuts the counts at the rectangular component.

hist(log10(ave.counts), breaks=100, main="", col="grey80",
     xlab=expression(Log[10]~"average count"))
abline(v=log10(0.01), col="blue", lwd=2, lty=2)

We will look at the identities of the most highly expressed genes before filtering them. This should generally be dominated by constitutively expressed transcripts, such as those for ribosomal or mitochondrial proteins. The presence of other classes of features may be cause for concern if they are not consistent with expected biology. For example, the absence of ribosomal proteins and/or the presence of their pseudogenes are indicative of suboptimal alignment.

plotHighestExprs(cdScFiltAnnot)

pdf('Highest_expression_50Gene.pdf')
plotHighestExprs(cdScFiltAnnot)
dev.off()
null device 
          1 

Percentage of total counts assigned to the top 50 most highly-abundant features in the dataset.

For each feature, each bar represents the percentage assigned to that feature for a single cell, while the circle represents the average across all cells. Bars are coloured by the total number of expressed features in each cell, while circles are coloured according to whether the feature is labelled as a control feature.

Do the topmost genes appear to agree with the biology?

Louisa: Please look at the genes whether those make sense…._

Now for the filteirng, I generally prefer the mean-based filter as it tends to be less aggressive. A gene will be retained as long as it has sufficient expression in any subset of cells. Genes expressed in fewer cells require higher levels of expression in those cells to be retained, but this is not undesirable as it avoids selecting uninformative genes (with low expression in few cells) that contribute little to downstream analyses, e.g., HVG detection or clustering. In contrast, the “at least n” filter depends heavily on the choice of n. With n = 10, a gene expressed in a subset of 9 cells would be filtered out, regardless of the level of expression in those cells. This may result in the failure to detect rare subpopulations that are present at frequencies below n. While the mean-based filter will retain more outlier-driven genes, this can be handled by choosing methods that are robust to outliers in the downstream analyses.

Thus, we apply the mean-based filter to the data by subsetting the SCESet object as shown below. This removes all rows corresponding to endogenous genes or spike-in transcripts with abundances below the specified threshold.

cdScFilt <- cdScFilt[keep,]
cdScFiltAnnot <- cdScFilt
#cdScFiltAnnot <- calculateQCMetrics(cdScFiltAnnot)
cdScFiltAnnot
class: SingleCellExperiment 
dim: 13210 19246 
metadata(0):
assays(2): counts logcounts
rownames(13210): AL669831.5 LINC00115 ... AL354822.1 AC240274.1
rowData names(9): symbol is_feature_control_Mt ... total_counts log10_total_counts
colnames(19246): AAACCCACAGTTAGGG-1 AAACCCACATGTGTCA-1 ... TTTGTTGGTCCTGGTG-4 TTTGTTGTCAGATTGC-4
colData names(39): barcode Sample ... pct_counts_in_top_500_features CellCycle
reducedDimNames(0):
spikeNames(0):

The filtering now lets see how the total counts and features plots:

p <- plotColData(cdScFilt, x="log10_total_counts",y="total_features_by_counts", colour_by = "Sample")  + scale_fill_manual(values=c_sample_col) 
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.
p

What is the frequency of the cells with mean expression?:

plotExprsFreqVsMean(cdScFiltAnnot)

Overall looks a reasonable number of genes expressed in 50% of cells.

p <- plotColData(cdScFilt, x="total_counts",y="total_features_by_counts", colour_by = "Sample") + scale_colour_manual(values=c_sample_col) 
p2 <- plotRowData(cdScFilt, x = "log10_total_counts", y = "n_cells_by_counts", colour_by = "pct_dropout_by_counts")
multiplot(plotPCA(cdScFilt, colour_by="Sample",
     size_by="total_features_by_counts") + scale_fill_manual(values=c_sample_col) ,
          p,
          p2,
          cols=2)
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.

summary(rowData(cdScFilt)$pct_dropout_by_counts)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
 0.05195 74.86492 88.77546 81.49701 95.74891 99.71945 

Some of the genes in the top right figure still has high drop-outs. This is not unusual for a single-cell RNA-seq as the data is very sparse and specially when using UMI based counts. If there are genes that have very high read counts, but are then expressed only very few cells, that would be worrying as it would indicate that those cells are behaving abruptly.

So things look fine in QC.

Normalization of cell-specific biases

Now, we would be doing step size based normalization. One of the widely used step-wise normalization technique for Bulk data is the one used by DESeq2. However, as the single cell data is very sparse we cannot use this method. The other very efficient method is the deconvolution based method which we would be using here.

Using the deconvolution method to deal with zero counts: Read counts are subject to differences in capture efficiency and sequencing depth between cells (Stegle et al., 2015). Normalization is required to eliminate these cell-specific biases prior to downstream quantitative analyses. This is often done by assuming that most genes are not differentially expressed (DE) between cells. Any systematic difference in count size across the non-DE majority of genes between two cells is assumed to represent bias and is removed by scaling. More specifically, “size factors” are calculated that represent the extent to which counts should be scaled in each library.

Single-cell data can be problematic due to the dominance of low and zero counts. To overcome this, we pool counts from many cells to increase the count size for accurate size factor estimation (Lun et al., 2016). Pool-based size factors are then “deconvolved” into cell-based factors for cell-specific normalization.

cdScFiltAnnot <- computeSumFactors(cdScFiltAnnot, sizes=c(100, 200, 300, 400, 440, 480, 500, 550))
summary(sizeFactors(cdScFiltAnnot))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
0.06796 0.65800 0.93359 1.00000 1.24521 5.71193 

If the size factors are tightly correlated with the library sizes for all cells this would suggests that the systematic differences between cells are primarily driven by differences in capture efficiency or sequencing depth. Any DE between cells would yield a non-linear trend between the total count and size factor, and/or increased scatter around the trend.

DF <- data.frame(VAR1=sizeFactors(cdScFiltAnnot), VAR2=cdScFiltAnnot$total_counts/1e6)
model = lm(VAR2 ~ VAR1, DF)
summary(model)

Call:
lm(formula = VAR2 ~ VAR1, data = DF)

Residuals:
       Min         1Q     Median         3Q        Max 
-0.0083548 -0.0007298 -0.0001815  0.0005718  0.0087967 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 7.185e-04  1.911e-05   37.59   <2e-16 ***
VAR1        6.324e-03  1.713e-05  369.10   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.001175 on 19244 degrees of freedom
Multiple R-squared:  0.8762,    Adjusted R-squared:  0.8762 
F-statistic: 1.362e+05 on 1 and 19244 DF,  p-value: < 2.2e-16
sp = ggplot(DF, aes(x=VAR1, y=VAR2))
sp + geom_point() + stat_smooth(method=lm) + theme_light(base_size=15) +
        theme(strip.background = element_blank(),
              panel.border     = element_blank(),
              plot.title = element_text(hjust = 0.5)) +  
              xlab("Size Factor") + 
              ylab("Library Size (millions)")+
              annotate("text", label="R^2=0.8762", x=2.8, y=0.075)

With a high R^2 value, the size factors are very tightly correlated with the library size. I calculate the size factor normization and put it in a slot of SummarizedExperiment object.

Applying the size factors to normalize gene expression:

The count data are used to compute normalized log-expression values for use in downstream analyses. Each value is defined as the log-ratio of each count to the size factor for the corresponding cell, after adding a prior count of 1 to avoid undefined values at zero counts. Division by the size factor ensures that any cell-specific biases are removed. If spike-in-specific size factors are present in sce, they will be automatically applied to normalize the spike-in transcripts separately from the endogenous genes.

#cdScFiltAnnot<- normalize(cdScFiltAnnot)
norm_exprs(cdScFiltAnnot)<- scater::normalize(cdScFiltAnnot)

Re-calculate the CPM normalization

I recalculate the counts-per-million for these cells. This is because large number of genes have been filtered out which would have impacted the library size when CPM was calcualted with unfiltered data. Now, as the number of genes have been reduced, so is the library size.

# cpm(cdScFiltAnnot) <- log2(calculateCPM(cdScFiltAnnot, use_size_factors = TRUE) + 1)
exprs(cdScFiltAnnot) <- log2(calculateCPM(cdScFiltAnnot, use_size_factors = TRUE) + 1)

I set the parameter use.size.factors = TRUE. This would construct the effective library size instead of the sum of counts as the library size. This effective library size is defined based on the step size calcualted as described in the pool based method paper.

The log-transformation provides some measure of variance stabilization (Law et al., 2014), so that high-abundance genes with large variances do not dominate downstream analyses. The computed values are stored as an exprs matrix in addition to the other assay elements.

Checking for important technical factors

We check whether there are technical factors that contribute substantially to the heterogeneity of gene expression. If so, the factor may need to be regressed out to ensure that it does not inflate the variances or introduce spurious correlations. For this dataset, the simple experimental design means that there are no plate or batch effects to examine. However there could be other technical factors like the cell-cycle effect or dropouts

plotExplanatoryVariables(cdScFiltAnnot, variables=c("total_features_by_counts", "total_counts", "CellCycle", "pct_counts_Mt", "Sample"))

Among the important technical factors, we can see that number of total features (genes) and total counts are explaining the major variability in the dataset. This confirms that cells would be mostly varied based on number of genes that they are expressing. In the cell-cycle plot showed earlier, we showed that there is cell-cycle effect but it might not be confounding the analysis.

Number of total features explaining majority of the variability is a common issue in single-cell RNA-seq. This could be due to the sparsity of the data. We need to keep in mind that cells in the PCA plot might be seperating just because they have very different number of features expressed in total.

However, I am expecting this would not confound the t-SNE plot that would take the non-linear relationship between the variables.

Identifying HVGs from the normalized log-expression

This is one of the very important excercies.

I now identify HVGs to focus on the genes that are driving heterogeneity across the population of cells. This requires estimation of the variance in expression for each gene, followed by decomposition of the variance into biological and technical components. HVGs are then identified as those genes with the largest biological components. This avoids prioritizing genes that are highly variable due to technical factors, such as sampling noise during RNA capture and library preparation.

In the recent implementation of seurat, Rahul Satija took a slightly different approach for HVG calculation. They calculate the variance and mean for each gene in the dataset (storing this in object@hvg.info), and sorts genes by their variance/mean ratio (VMR). They have observed that for large-cell datasets with unique molecular identifiers, selecting highly variable genes (HVG) simply based on VMR is an efficient and robust strategy.

But in our implementation we fit the trend to the variance estimates of the endogenous genes, using the use.spikes=FALSE setting as shown below. This assumes that the majority of genes are not variably expressed, such that the technical component dominates the total variance for those genes. The fitted value of the trend is then used as an estimate of the technical component.

span: Low-abundance genes with mean log-expression below min.mean are not used in trend fitting, to preserve the sensitivity of span-based smoothers at moderate-to-high abundances. It also protects against discreteness, which can interfere with estimation of the variability of the variance estimates and accurate scaling of the trend. The default threshold is chosen based on the point at which discreteness is observed in variance estimates from Poisson-distributed counts. For heterogeneous droplet data, a lower threshold of 0.001-0.01 should be used.

var.fit <- trendVar(cdScFiltAnnot, method="loess", use.spikes=FALSE, min.mean=1.0)
var.out <- decomposeVar(cdScFiltAnnot, var.fit)

We assess the suitability of the trend fitted to the endogenous variances by examining whether it is consistent with the variances. The trend passes through or close to most of the endogenous gene variances, indicating that our assumption (that most genes have low levels of biological variability) is valid. This strategy exploits the large number of endogenous genes to obtain a stable trend.

plot(var.out$mean, var.out$total, pch=16, cex=0.6, xlab="Mean log-expression", ylab="Variance of log-expression")
o <- order(var.out$mean)
lines(var.out$mean[o], var.out$tech[o], col="dodgerblue", lwd=2)

Ideally the plot above would look like the mountain, where it would have rise in the middle but then would be dropping at the end as we have low variance for highly expressed genes. The trend line would follow it as well.

HVGs are defined as genes with biological components that are significantly greater than zero at a false discovery rate (FDR) of 5%. These genes are interesting as they drive differences in the expression profiles between cells, and should be prioritized for further investigation. In addition, I only consider a gene to be a HVG if it has a biological component greater than or equal to 0.5. For transformed expression values on the log2 scale, this means that the average difference in true expression between any two cells will be at least 2-fold. (This reasoning assumes that the true log-expression values are Normally distributed with variance of 0.5. The root-mean-square of the difference between two values is treated as the average log2-fold change between cells and is equal to unity.) I rank the results by the biological component to focus on genes with larger biological variability.

hvg.out <- var.out[which(var.out$FDR <= 0.05 & var.out$bio >= 0.5),]
hvg.out <- hvg.out[order(hvg.out$bio, decreasing=TRUE),]
nrow(hvg.out)
[1] 632

So, there are 632 HVGs for this dataset.

Plotting the HVGs. The red dots are the HVGs we selected.

plot(var.out$mean, var.out$total, pch=16, cex=0.6, xlab="Mean log-expression",
     ylab="Variance of log-expression")
o <- order(var.out$mean)
lines(var.out$mean[o], var.out$tech[o], col="dodgerblue", lwd=2)
points(var.out[rownames(hvg.out),]$mean, var.out[rownames(hvg.out),]$total, col="red", pch=16, cex=0.6)

Plotting the top 15 HVGs:

plotExpression(cdScFiltAnnot, rownames(hvg.out)[1:15])

Discuss meaning of genes.

write.table(file="HVG_Louisa_Nelson_10X_Dataset.tsv", hvg.out, sep="\t", quote = FALSE, col.names = NA)
head(hvg.out, 20)
DataFrame with 20 rows and 6 columns
                     mean            total              bio             tech               p.value                   FDR
                <numeric>        <numeric>        <numeric>        <numeric>             <numeric>             <numeric>
TIMP1    8.22822611977597 23.2445946615811 13.4772653842641 9.76732927731701                     0                     0
KRT81    3.79280737225925 26.3679279637404 11.4369375039588 14.9309904597816                     0                     0
TPT1     9.99163026093074 18.3841149265066 10.5915443828566 7.79257054365002                     0                     0
FN1      7.41597123112208 21.4155024164406  10.560609699778 10.8548927166626                     0                     0
IFI27    4.60730444445383 23.7733388099039 8.62808568555934 15.1452531243445                     0                     0
...                   ...              ...              ...              ...                   ...                   ...
TFPI2    5.29991228294919 19.7089953327959 5.56456654848151 14.1444287843144 2.25433612863366e-260 1.24082417746878e-257
IGFBP5   2.17462766498083 17.1101529380375 5.54840396255214 11.5617489754854                     0                     0
SERPINE1 8.10494233613672 15.2842598771331 5.37040136183392 9.91385851529918                     0                     0
SERPINE2 3.52377909901619 19.9367397348913 5.27800359109321 14.6587361437981 3.35932681708548e-222 1.47922357512331e-219
CCDC80    5.4689651815356 19.0482949418358 5.26705547316059 13.7812394686752 2.93700403144984e-247 1.49222397136356e-244

There are other alternative approaches for determining the HVGs specially those based on Coefficient of Variance. Here I use the variance of the log-expression values because the log-transformation protects against genes with strong expression in only one or two cells. This ensures that the set of top HVGs is not dominated by genes with (mostly uninteresting) outlier expression patterns.

However, I would like to also test other methods of identifying the HVGs. It has been mentioned that fitting the trendline to endogenous genes might not always be a good idea.

Downstream analysis with HVG genes

I will see instead of taking the correlated genes how the result comes up with the HVGs only.

rowVarsSorted <- exprs(cdScFiltAnnot)[rownames(hvg.out),]
FinalPCAData <- t(rowVarsSorted)
pcaPRComp <- prcomp(FinalPCAData)
nmax = 10
if(dim(pcaPRComp$x)[1] < 10){ nmax = dim(pcaPRComp$x)[1] }
txt1 <- paste("Percent_PC_Var_onfirst",nmax,"PCs",sep="")
pca_var = pcaPRComp$sdev ^ 2
pca_var_percent <- 100 * pca_var / sum(pca_var)
pca_var_percent_first10 <- NA * pca_var
pca_var_percent_first10[1:nmax] <- 100 * pca_var[1:nmax] / sum(pca_var[1:nmax])
#pca_corr_reads <- apply(pcaPRComp$x,2,function(x) cor(x,report_sub$Assigned))
    
pca_var_out <- data.frame(round(pca_var,3),round(pca_var_percent,1),
                          round(pca_var_percent_first10,1))
#rownames(pca_var_out) <- rownames(pcaPRComp$x)
colnames(pca_var_out) <- c("PC_Var","PC_Var_percent",txt1)
nColToDisplay = 5
df <- as.data.frame(pcaPRComp$x)
df$Cell=as.factor(colData(cdScFiltAnnot)$Sample)
p <- ggpairs(df, columns=1:nColToDisplay, upper=list(continuous="points"), 
             title='Plotting first four PCAs', 
             mapping = aes_string(color="Cell"),
             legend = c(1,nColToDisplay),
             columnLabels = as.character(paste0(colnames(df[,1:nColToDisplay]), ' : ', 
                                                pca_var_out$PC_Var_percent[1:nColToDisplay], '% variance')))+
    theme_light(base_size=15)+     
    theme(plot.title = element_text(hjust = 0.5))
for(i in 1:p$nrow) {
  for(j in 1:p$ncol){
    p[i,j] <- p[i,j] + 
        scale_fill_manual(values=c_sample_col) +
        scale_colour_manual(values=c_sample_col)  
  }
}
    
print(p)

The results does not do much with the chosen and the HVG ones. So for the downstream analysis I will take the HVG genes only instead of the correlated HVGs.

t-SNE plot with only the HVG genes:

tsne_out <- Rtsne(as.matrix(pcaPRComp$x[,1:14]),check_duplicates = FALSE, pca = FALSE,
             perplexity=30, theta=0.01, dims=2, num_threads = 8)
Rep <- as.factor(colData(cdScFiltAnnot)$Sample)
counts <- colData(cdScFiltAnnot)$total_counts
p <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=counts)) +
     geom_point(size=0.75) +
     guides(colour = guide_legend(override.aes = list(size=0.5))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of Sample Data") +
     theme_classic(base_size=14) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank())
p2 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Rep)) +
     geom_point(size=0.75) +
     guides(colour = guide_legend(override.aes = list(size=0.8))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of Expression Data") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) +
          scale_fill_manual(values=c_sample_col) +
         scale_colour_manual(values=c_sample_col)
p2

#multiplot(p,p2,cols=2)
save.image()

So the t-SNe plot clearly seperates the patients. This is what we generally see when we have multiple patients. The gene expression profile for every patient itself is very different for cells and that is why cells from different patients come differently. One interesting thing is a cluster with all the cells mixed. Now, this could be the 25% of stromal cells from each patient where as the heterogeneity in tumour samples seperate the patients. ### Running Umap for data visualization

embedding <- umap(pcaPRComp$x[,1:14])
as.tibble(embedding$layout) %>%
  mutate(Samples = colData(cdScFiltAnnot)$Sample) %>%
  ggplot(aes(V1, V2, color=Samples)) +  geom_point(size=0.75) + theme_classic() +scale_colour_manual(values=c_sample_col) 

Clustering

There are numerous methods of clustering, the dynamic-cut-tree, the k-mean, k-medoids, GMM, GP-LVM and so on. I will straight jump into dynamic-cut-tree as this is the one that performed best while I was doing analysis with the naive and Aspd12 dataset.

Dynamic Cut Tree

Hierarchical clustering is a widely used method for detecting clusters in genomic data. Clusters are defined by cutting branches off the dendrogram. A common but inflexible method uses a constant height cutoff value; this method exhibits suboptimal performance on complicated dendrograms. We present the Dynamic Tree Cut R library that implements novel dynamic branch cutting methods for detecting clusters in a dendrogram depending on their shape. Compared to the constant height cutoff method, our techniques offer the following advantages: (1) they are capable of identifying nested clusters; (2) they are flexible — cluster shape parameters can be tuned to suit the application at hand; (3) they are suitable for automation; and (4) they can optionally combine the advantages of hierarchical clustering and partitioning around medoids, giving better detection of outliers.

Clustering cells into putative subpopulations

We perform hierarchical clustering on the Euclidean distances between cells, using Ward’s criterion to minimize the total variance within each cluster. This yields a dendrogram that groups together cells with similar expression patterns across the chosen genes. An alternative approach is to cluster on a matrix of distances derived from correlations (e.g., as in quickCluster). This is more robust to noise and normalization errors, but is also less sensitive to subtle changes in the expression profiles.

chosen.exprs <- logcounts(cdScFiltAnnot[rownames(hvg.out),])
my.dist <- dist(t(chosen.exprs))
my.tree <- hclust(my.dist, method = "ward.D2")

Clusters are explicitly defined by applying a dynamic tree cut (Langfelder et al., 2008)[https://academic.oup.com/bioinformatics/article/24/5/719/200751] to the dendrogram. This exploits the shape of the branches in the dendrogram to refine the cluster definitions, and is more appropriate than cutree for complex dendrograms. Greater control of the empirical clusters can be obtained by manually specifying cutHeight in cutreeDynamic.

library(dynamicTreeCut)
my.clusters <- unname(cutreeDynamic(my.tree, distM=as.matrix(my.dist), verbose=0))

Number of clusters chosen by this method is:

levels(as.factor(my.clusters))
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"
cdScFiltAnnot$Clusters <- as.factor(my.clusters)

Drawing the heatmap, first scale te:

library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 2.0.0
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.
========================================
heat.vals <- chosen.exprs - rowMeans(chosen.exprs)
#clust.col <- rainbow(max(my.clusters))
#heatmap.2(heat.vals, col=bluered, symbreak=TRUE, trace='none', cexRow=0.3,
#    ColSideColors=clust.col[my.clusters], Colv=as.dendrogram(my.tree))
df = data.frame(Class = as.factor(my.clusters))
ha = HeatmapAnnotation(df = df)
#ha = HeatmapAnnotation(df = df, col = list(Condition = c("MC_A" =  "dodgerblue", "MC_B"= "firebrick", "MC_C"="forestgreen", "MC_D"="gold")))
Heatmap(heat.vals , top_annotation = ha, show_column_names=FALSE, cluster_rows = TRUE, clustering_method_columns = "ward.D2", row_names_gp = gpar(fontsize = 8))

Saving the heatmap for later exploration:

pdf("Matthew_Hepworth_heat_newDataset.pdf", width=20, height=40)
Heatmap(heat.vals , top_annotation = ha, show_column_names=FALSE, cluster_rows = TRUE, clustering_method_columns = "ward.D2", row_names_gp = gpar(fontsize = 8))
dev.off()
null device 
          1 

t-SNE with cluster allocation:

Clusters <- as.factor(my.clusters)
p2 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Clusters)) +
     geom_point(size=1.0) +
     guides(colour = guide_legend(override.aes = list(size=1))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of cluster Data") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) + scale_color_manual(values=c_clust_col)
p2

#multiplot(p,p2,cols=2)
Clusters <- as.factor(my.clusters)
as.tibble(embedding$layout) %>%
  mutate(Clusters = Clusters) %>%
  ggplot(aes(V1, V2, color=Clusters)) +  geom_point(size=0.75) + theme_classic() + scale_color_manual(values=c_clust_col)

as.tibble(embedding$layout) %>%
  mutate(Samples = colData(cdScFiltAnnot)$Sample) %>%
  ggplot(aes(V1, V2, color=Samples)) +  geom_point(size=0.75) + theme_classic() + scale_color_manual(values=c_clust_col)

How the clustering is impacted with read counts

as.tibble(embedding$layout) %>%
  mutate(ReadDepth = cdScFiltAnnot$log10_total_counts) %>%
  ggplot(aes(V1, V2, color=ReadDepth)) +  geom_point(size=0.75) + theme_classic() +
  scale_colour_gradientn(colours = rainbow(5))

as.tibble(as.data.frame(tsne_out$Y)) %>%
  mutate(ReadDepth = cdScFiltAnnot$log10_total_counts) %>%
  ggplot(aes(V1, V2, color=ReadDepth)) +  geom_point(size=0.75) + theme_classic() +
  scale_colour_gradientn(colours = rainbow(5))

p1<-as.tibble(as.data.frame(tsne_out$Y)) %>%
  mutate(ReadDepth = cdScFiltAnnot$log10_total_counts) %>%
  ggplot(aes(V1, V2, color=ReadDepth)) +  geom_point(size=0.75) + theme_classic() +
  scale_colour_gradientn(colours = rainbow(5))
p2<-as.tibble(as.data.frame(tsne_out$Y)) %>%
  mutate(Clusters = cdScFiltAnnot$Clusters) %>%
  ggplot(aes(V1, V2, color=Clusters)) +  geom_point(size=0.75) + theme_classic() + scale_color_manual(values=c_clust_col)
#par(mfrow=c(1,2))
p1 

p2

One of my worry was that the clusters and the t-SNE seperation are dominated by the read depth. Although in t-SNE one can see the gradient of the Read Depth (read depths moving into one side) which has been reported in the literature Hafemeister et. al. 2019 but it surely is not dominating the clusters on the t-SNE seperation.

Cluster validation

It looks like that the clustering method that I prefer to use dynamicTreeCut is producing 12 clusters after removing the cells from 12th cluster from the first run. In this new clustering cluster-8 is spread everywhere and does not seem to be a valid cluster. So, I will not apply clustering validation methods to see this.

Internal measures for cluster validation

In this section, we describe the most widely used clustering validation indices. Recall that the goal of partitioning clustering algorithms (Part @ref(partitioning-clustering)) is to split the data set into clusters of objects, such that:

  • the objects in the same cluster are similar as much as possible,
  • and the objects in different clusters are highly distinct Cluster Validation-1 Cluster Validation-2 Cluster Validation-3

  • That is, we want the average distance within cluster to be as small as possible; and the average distance between clusters to be as large as possible.

Internal validation measures reflect often the compactness, the connectedness and the separation of the cluster partitions.

  1. Compactness or cluster cohesion: Measures how close are the objects within the same cluster. A lower within-cluster variation is an indicator of a good compactness (i.e., a good clustering). The different indices for evaluating the compactness of clusters are base on distance measures such as the cluster-wise within average/median distances between observations.
  2. Separation: Measures how well-separated a cluster is from other clusters. The indices used as separation measures include:
    • distances between cluster centers
    • the pairwise minimum distances between objects in different clusters
  3. Connectivity: corresponds to what extent items are placed in the same cluster as their nearest neighbors in the data space. The connectivity has a value between 0 and infinity and should be minimized.

Generally most of the indices used for internal clustering validation combine compactness and separation measures as follow:

\(Index = \frac{\alpha * Seperation}{\beta * Compactness}\) where \(\alpha\) and \(\beta\) are weights.

Silhouette coefficient

The silhouette analysis measures how well an observation is clustered and it estimates the average distance between clusters. The silhouette plot displays a measure of how close each point in one cluster is to points in the neighboring clusters.

For each observation \(i\), the silhouette width \(s_i\) is calculated as follows:

  1. For each observation \(i\), calculate the average dissimalirty \(\alpha_i\) between \(i\) and all other points of the cluster which \(i\) belongs.
  2. For all other clusters \(C\), to which \(i\) does not belong, calculate the average dissimilarity \(d(i,C)\) of \(i\) to all observations of \(C\). The smallest of these \(d(i,C)\) is defined as \(b_i = min_C d(i,C)\). The value of \(b_i\) can be seen as the dissimilarity between i and its neighbor cluster, i.e. the nearest one to which it does not belong.
  3. Finally the silhouette width of the observation \(i\) is defined by the formula: \(S_i = \frac{(b_i - a_i)}{max(a_i,b_i)}\)

Silhouette width can be interpreted as follow:

  • Observations with a large Si (almost 1) are very well clustered.
  • A small Si (around 0) means that the observation lies between two clusters.
  • Observations with a negative Si are probably placed in the wrong cluster.
library(cluster)
library(factoextra)
Welcome! Related Books: `Practical Guide To Cluster Analysis in R` at https://goo.gl/13EFCZ
#my.clusters <- unname(cutreeDynamic(my.tree, distM=as.matrix(my.dist), verbose=0))
fviz_silhouette(silhouette(my.clusters, my.dist), print.summary = FALSE)

It looks like that the stability of cluster 7, 8 & 10 is quite low. Something to keep in mind.

Now, how does the TP53 and XIST expression looks like

GeneExp <- logcounts(cdScFiltAnnot)['XIST',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['XIST',]
p1<-     ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.8) +
      scale_colour_gradient(low = "gray88", high = "purple4")+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','XIST'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
p1

GeneExp <- logcounts(cdScFiltAnnot)['TP53',]
    #GeneName = 'SPN'
    df <- as.data.frame(tsne_out$Y)
    df[,'GeneExp']=logcounts(cdScFiltAnnot)['TP53',]
  p2 <-  ggplot(df, aes(x=V1, y=V2, GeneExp = GeneExp)) +
      geom_point(size=1.00,aes(colour = GeneExp), alpha=0.3) +
      scale_colour_gradient(low = "gray88", high = "purple4")+
      #guides(colour = guide_legend(override.aes = list(size=4))) +
      xlab("") + ylab("") +
      ggtitle(paste0('Gene Exp:','TP53'))+
      theme_classic(base_size=14) +
      theme(strip.background = element_blank(),
            strip.text.x     = element_blank(),
            axis.text.x      = element_blank(),
            axis.text.y      = element_blank(),
            axis.ticks       = element_blank(),
            axis.line        = element_blank(),
            panel.border     = element_blank())
  p2

p3 <- ggplot(as.data.frame(tsne_out$Y), aes(x=V1, y=V2, color=Rep)) +
     geom_point(size=0.75) +
     guides(colour = guide_legend(override.aes = list(size=0.8))) +
     xlab("") + ylab("") +
     ggtitle("t-SNE 2D Embedding of Expression Data") +
     theme_classic(base_size=10) +
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) +
          scale_fill_manual(values=c_sample_col) +
         scale_colour_manual(values=c_sample_col)
p3

p4<-as.tibble(as.data.frame(tsne_out$Y)) %>%
  mutate(Clusters = cdScFiltAnnot$Clusters) %>%
  ggplot(aes(x=V1, y=V2, color=Clusters)) +  
  xlab("") + ylab("") +
  guides(colour = guide_legend(override.aes = list(size=0.8))) +
  geom_point(size=0.75) + 
  theme_classic() + 
     theme(strip.background = element_blank(),
           strip.text.x     = element_blank(),
           axis.text.x      = element_blank(),
           axis.text.y      = element_blank(),
           axis.ticks       = element_blank(),
           axis.line        = element_blank(),
           panel.border     = element_blank()) +
    scale_color_manual(values=c_clust_col)
p4

multiplot(p1,p3,p2,p4,cols=2)

Identifying marker genes

Potential marker genes are identified by taking the top set of DE genes from each pairwise comparison between clusters. We arrange the results into a single output table that allows a marker set to be easily defined for a user-specified size of the top set. For example, to construct a marker set from the top 10 genes of each comparison, one would filter marker.set to retain rows with Top less than or equal to 10.

We save the list of candidate marker genes for further examination. We also examine their expression profiles to verify that the DE signature is robust. The heatmap figure below indicates that most of the top markers have strong and consistent up- or downregulation in cells of cluster 1 compared to some or all of the other clusters. Thus, cells from the subpopulation of interest can be identified as those that express the upregulated markers and do not express the downregulated markers.

library(edgeR)
cluster <- factor(my.clusters)
de.design <- model.matrix(~0 + cluster)
y <- convertTo(cdScFiltAnnot, type="edgeR")
y <- estimateDisp(y, de.design)
fit <- glmFit(y, de.design)
summary(y$tagwise.dispersion)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
  0.0001   0.1426   0.2572   0.6774   0.5837 102.4000 
clust.col <- rainbow(max(my.clusters))

Cluster1 marker genes

result1.logFC <- result1.FDR <- list()
chosen.clust <- which(levels(cluster)=="1") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result1.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result1.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result1.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker1.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result1.logFC), 
    FDR = result1.FDR, stringsAsFactors=FALSE)
marker1.set <- marker1.set[order(marker1.set$Top),]
marker1.set.pos <- marker1.set[rowSums(marker1.set[,3:11]>0)==9,]
marker1.set.pos <- marker1.set.pos[order(marker1.set.pos$Top),]
head(marker1.set, 10)
write.table(marker1.set, file="Louisa_Nelson_10XDataset_Cluster1.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker1.set.pos, file="Louisa_Nelson_10XDataset_Cluster1_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker1.set$Gene[marker1.set$Top <= 10]

Cluster2 marker genes

result2.logFC <- result2.FDR <- list()
chosen.clust <- which(levels(cluster)=="2") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result2.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result2.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result2.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker2.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result2.logFC), 
    FDR = result2.FDR, stringsAsFactors=FALSE)
marker2.set <- marker2.set[order(marker2.set$Top),]
marker2.set.pos <- marker2.set[rowSums(marker2.set[,3:11]>0)==9,]
marker2.set.pos <- marker2.set.pos[order(marker2.set.pos$Top),]
head(marker2.set, 10)
marker2.set.strong.gene <-  marker2.set[rowSums(abs(marker2.set[,3:13])>1)>6,]
head(marker2.set.strong.gene)
write.table(marker2.set.strong.gene, file="Cluster2_HighLo2Fold.txv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker2.set, file="Louisa_Nelson_10XDataset_Cluster2.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker2.set.pos, file="Louisa_Nelson_10XDataset_Cluster2_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker2.set$Gene[marker2.set$Top <= 10]

Cluster3 marker genes

result3.logFC <- result3.FDR <- list()
chosen.clust <- which(levels(cluster)=="3") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result3.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result3.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result3.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker3.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result3.logFC), 
    FDR = result3.FDR, stringsAsFactors=FALSE)
marker3.set <- marker3.set[order(marker3.set$Top),]
marker3.set.pos <- marker3.set[rowSums(marker3.set[,3:11]>0)==9,]
marker3.set.pos <- marker3.set.pos[order(marker3.set.pos$Top),]
head(marker3.set, 10)
write.table(marker3.set, file="Louisa_Nelson_10XDataset_Cluster3.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker3.set.pos, file="Louisa_Nelson_10XDataset_Cluster3_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker3.set$Gene[marker1.set$Top <= 10]

Cluster4 marker genes

result4.logFC <- result4.FDR <- list()
chosen.clust <- which(levels(cluster)=="4") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result4.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result4.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result4.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker4.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result4.logFC), 
    FDR = result4.FDR, stringsAsFactors=FALSE)
marker4.set <- marker4.set[order(marker4.set$Top),]
marker4.set.pos <- marker4.set[rowSums(marker4.set[,3:11]>0)==9,]
marker4.set.pos <- marker4.set.pos[order(marker4.set.pos$Top),]
head(marker4.set, 10)
write.table(marker4.set, file="Louisa_Nelson_10XDataset_Cluster4.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker4.set.pos, file="Louisa_Nelson_10XDataset_Cluster4_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker1.set$Gene[marker4.set$Top <= 10]

Cluster5 marker genes

result5.logFC <- result5.FDR <- list()
chosen.clust <- which(levels(cluster)=="5") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result5.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result5.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result5.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker5.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result5.logFC), 
    FDR = result5.FDR, stringsAsFactors=FALSE)
marker5.set <- marker5.set[order(marker5.set$Top),]
marker5.set.pos <- marker5.set[rowSums(marker5.set[,3:11]>0)==9,]
marker5.set.pos <- marker5.set.pos[order(marker5.set.pos$Top),]
head(marker5.set, 10)
#write.table(marker5.set, file="Louisa_Nelson_10XDataset_Cluster5.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker5.set.pos, file="Louisa_Nelson_10XDataset_Cluster5_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker5.set$Gene[marker5.set$Top <= 10]

Cluster6 marker genes

result6.logFC <- result6.FDR <- list()
chosen.clust <- which(levels(cluster)=="6") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result6.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result6.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result6.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker6.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result6.logFC), 
    FDR = result6.FDR, stringsAsFactors=FALSE)
marker6.set <- marker6.set[order(marker6.set$Top),]
marker6.set.pos <- marker6.set[rowSums(marker6.set[,3:11]>0)==9,]
marker6.set.pos <- marker6.set.pos[order(marker6.set.pos$Top),]
head(marker6.set, 10)
write.table(marker6.set, file="Louisa_Nelson_10XDataset_Cluster6.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker6.set.pos, file="Louisa_Nelson_10XDataset_Cluster6_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker6.set$Gene[marker1.set$Top <= 10]

Cluster7 marker genes

result7.logFC <- result7.FDR <- list()
chosen.clust <- which(levels(cluster)=="7") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result7.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result7.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result7.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker7.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result7.logFC), 
    FDR = result7.FDR, stringsAsFactors=FALSE)
marker7.set <- marker7.set[order(marker7.set$Top),]
marker7.set.pos <- marker7.set[rowSums(marker7.set[,3:11]>0)==9,]
marker7.set.pos <- marker7.set.pos[order(marker7.set.pos$Top),]
head(marker7.set, 10)
write.table(marker7.set, file="Louisa_Nelson_10XDataset_Cluster7.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker7.set.pos, file="Louisa_Nelson_10XDataset_Cluster7_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker7.set$Gene[marker7.set$Top <= 10]

Cluster8 marker genes

result8.logFC <- result8.FDR <- list()
chosen.clust <- which(levels(cluster)=="8") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result8.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result8.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result8.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker8.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result8.logFC), 
    FDR = result8.FDR, stringsAsFactors=FALSE)
marker8.set <- marker8.set[order(marker8.set$Top),]
marker8.set.pos <- marker8.set[rowSums(marker8.set[,3:11]>0)==9,]
marker8.set.pos <- marker8.set.pos[order(marker8.set.pos$Top),]
head(marker8.set, 10)
write.table(marker8.set, file="Louisa_Nelson_10XDataset_Cluster8.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker8.set.pos, file="Louisa_Nelson_10XDataset_Cluster8_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker8.set$Gene[marker8.set$Top <= 10]

Cluster9 marker genes

result9.logFC <- result9.FDR <- list()
chosen.clust <- which(levels(cluster)=="9") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result9.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result9.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result9.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker9.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result9.logFC), 
    FDR = result9.FDR, stringsAsFactors=FALSE)
marker9.set <- marker9.set[order(marker9.set$Top),]
marker9.set.pos <- marker9.set[rowSums(marker9.set[,3:11]>0)==9,]
marker9.set.pos <- marker9.set.pos[order(marker9.set.pos$Top),]
head(marker9.set, 10)
write.table(marker9.set, file="Louisa_Nelson_10XDataset_Cluster9.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker9.set.pos, file="Louisa_Nelson_10XDataset_Cluster9_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker9.set$Gene[marker1.set$Top <= 10]

Cluster10 marker genes

result10.logFC <- result10.FDR <- list()
chosen.clust <- which(levels(cluster)=="10") # character, as ’cluster’ is a factor.
for (clust in seq_len(nlevels(cluster))) {
    if (clust==chosen.clust) { next }
    contrast <- numeric(ncol(de.design))
    contrast[chosen.clust] <- 1
    contrast[clust] <- -1
    fit <- glmQLFit(y, de.design)
    res <- glmQLFTest(fit, contrast = contrast)
    top.tags <- topTags(res, n=length(y$design), sort.by="none")
    
    con.name <- paste0('vs.', levels(cluster)[clust])
    result10.logFC[[con.name]] <- top.tags$table$logFC
    #names(result1.logFC[[con.name]]) <- rownames(top.tags$table)
    result10.FDR[[con.name]] <- top.tags$table$FDR
    #names(result1.FDR[[con.name]]) <- rownames(top.tags$table)
}
collected.ranks <- lapply(result10.FDR, rank, ties="first")
min.rank <- do.call(pmin, collected.ranks)
marker10.set <- data.frame(Top=min.rank, Gene=rownames(y),
    logFC=do.call(cbind, result10.logFC), 
    FDR = result10.FDR, stringsAsFactors=FALSE)
marker10.set <- marker10.set[order(marker10.set$Top),]
marker10.set.pos <- marker10.set[rowSums(marker10.set[,3:11]>0)==9,]
marker10.set.pos <- marker10.set.pos[order(marker10.set.pos$Top),]
head(marker10.set, 10)
write.table(marker10.set, file="Louisa_Nelson_10XDataset_Cluster10.tsv", sep="\t", quote=FALSE, col.names=NA)
write.table(marker10.set.pos, file="Louisa_Nelson_10XDataset_Cluster10_pos.tsv", sep="\t", quote=FALSE, col.names=NA)
top.markers <- marker10.set$Gene[marker10.set$Top <= 10]
LS0tCnRpdGxlOiAiTG91aXNhIE5lbHNvbiBzY1JOQS1zZXEgZm9yIG92YXJpYW4gY2FuY2VyIG1vZGVsIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogbm8KICAgICAgc21vb3RoX3Njcm9sbDogbm8KICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwotLS0KIyBQcm9qZWN0IERlc2NyaXB0aW9uCgpUaGVzZSBzYW1wbGVzIGFyZSBiYXNpY2FsbHkgYW4gYWRkaXRpb24gdG8gIHRoZSBvbmUgc2FtcGxlIGluIHRoZSBwYXBlciB0aGF0IHdhcyBhbmFseXNlZCBwcmV2aW91c2x5LiBUaGV5IGFyZSBjZWxscyBmcm9tIHBhdGllbnRzIHdpdGggb3ZhcmlhbiBjYW5jZXIsIHdlIGlzb2xhdGVkIHRoZSB0dW1vdXIgYW5kIHN0cm9tYWwgY2VsbHMgZm9yIGVhY2ggcGF0aWVudC4gQW5kIGhhdmUgbWl4ZWQgdGhlbSBiYWNrIHRvZ2V0aGVyIHRvIGdpdmUgMSBzYW1wbGUgcGVyIHBhdGllbnQuCgpTbyB0aGUgb25seSBkaWZmZXJlbmNlIGZyb20gdGhlIHByZXZpb3VzIHNhbXBsZSBpcyB0aGF0IHRoZXNlIGhhdmUgdGhlIHR1bW91ciBhbmQgc3Ryb21hbHMgZnJvbSB0aGUgc2FtZSBwYXRpZW50IG1peGVkIHRvZ2V0aGVyIGluIGEgYDc1JToyNSVgIHJhdGlvCgojIyBBaW0KVGhlIGFpbXMgd2VyZQoKICAgIDEuIFRvIGNsdXN0ZXIgdGhlbSBlc3RhYmxpc2ggdGhlIDIgcG9wdWxhdGlvbnMKICAgIDIuIFRvIGVzdGFibGlzaCB0aGUgb3Zlci1kaXNwZXJzZWQgZ2VuZXMgaW4gdGhlIHR3byBwb3B1bGF0aW9ucwoKRnJvbSB0aGUgZmlyc3Qgc2FtcGxlIGBwNTNgIGFuZCBgWElTVGAgd2VyZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgc28geW91IGNvdWxkIHVzZSB0aGVzZSB0byB2YWxpZGF0ZSB0aGUgaW5pdGlhbCBjbHVzdGVyaW5nLiBUaGUgYW5hbHlzaXMgYWZ0ZXIgdGhhdCB3b3VsZCBiZSB0aGUgc2FtZSBhcyB0aGUgcHJldmlvdXMgc2FtcGxlCgoKIyBEYXRhIHByZXBlcmF0aW9uCiMjIExvYWRpbmcgbGlicmFyeQpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KG1jbHVzdCkKbGlicmFyeShSdHNuZSkKbGlicmFyeSh2aXJpZGlzKQojbGlicmFyeSh1bWFwcikKbGlicmFyeSh1bWFwKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYWxldHRlZXIpCmBgYAoKU2V0dGluZyB1cCBjb2xvciBwYWxldHRlcy4gSSB3YXMgdXNpbmcgcGFsZXR0ZXIgcGFja2FnZSBhcyBpdCB3YXMgZ2l2aW5nIGEgY29sb3VyIGJsaW5kIHBhbGV0dGUuIEJ1dCBub3cgSSBhbSB1c2luZyB0aGUgcGFsZXR0ZSB0aGF0IE1hdHQgZ2F2ZSBmcm9tIENvbm9yLiBUaGlzIGdpdmVzIGEgdXNlZnVsIGNsdXN0ZXJpbmcgZm9yIE1hdHQKClNldHRpbmcgdXAgY29sb3JibGluZCBmcmllbmRseSBjb2xvciBwYWxldHRlOgoKYGBge3J9CiNjYlBhbGV0dGUgPC0gcGFsZXR0ZWVyX2QocGFja2FnZSA9ICJnZ3RoZW1lcyIsIHBhbGV0dGU9ImNhbGMiLCBuPTEyKQogYzMwIDwtIGMoImRvZGdlcmJsdWUyIiwjMQogICAgICAgICAiI0UzMUExQyIsICMyIHJlZAogICAgICAgICAgImdyZWVuNCIsICMzCiAgICAgICAgICIjRkY3RjAwIiwgIzQgb3JhbmdlCiAgICAgICAgICAgImdyZWVuMSIsIzUKICAgICAgICAgInB1cnBsZSIsIzYKICAgICAgICAgImJsdWUxIiwjNwogICAgICAgICAiZGVlcHBpbmsxIiwjOAogICAgICAgICAiZGFya29yYW5nZTQiLCM5CiAgICAgICAgICAiYmxhY2siLCMxMAogICAgICAgICAiZ29sZDEiLCMxMQogICAgICAgICAiZGFya3R1cnF1b2lzZSIsIzEyCiAgICAgICAgICIjNkEzRDlBIiwgIzEzIHB1cnBsZQogICAgICAgICAib3JjaGlkMSIsIzE0CiAgICAgICAgICJncmF5NzAiLCMxNQogICAgICAgICAgIm1hcm9vbiIsIzE2CiAgICAgICAgICJwYWxlZ3JlZW4yIiwjMTcKICAgICAgICAgICIjMzMzMzMzIiwjMTgKICAgICAgICAgICIjQ0FCMkQ2IiwgIzE5IGx0IHB1cnBsZQogICAgICAgICAgIiNGREJGNkYiLCAjMjAgbHQgb3JhbmdlCiAgICAgICAgICJraGFraTIiLCMyMQogICAgICAgICAic2t5Ymx1ZTIiLCMyMgogICAgICAgICAic3RlZWxibHVlNCIsIzIzCiAgICAgICAgICJncmVlbjEiLCMyNAogICAgICAgICAieWVsbG93NCIsIzI1CiAgICAgICAgICJ5ZWxsb3czIiwjMjYKICAgICAgICAgIiNGQjlBOTkiLCAjMjcgbHQgcGluawogICAgICAgICAiYnJvd24iLCMyOAogICAgICAgICAiIzAwMDA5OSIsIzI5CiAgICAgICAgICIjQ0MzMzAwIiMzMAogICAgICAgICApCiAKcGllKHJlcCgxLDMwKSwgY29sPWMzMCkKYGBgCkNob29zaW5nIGZvciB0aGUgc2FtcGxlcwoKYGBge3J9CmNfc2FtcGxlX2NvbCA8LSBjMzBbYygzLDIyLDE5LDMwKV0KYGBgCgpDaG9vc2luZyBmb3IgdGhlIGNsdXN0ZXJzCmBgYHtyfQpjX2NsdXN0X2NvbCA8LSBjMzBbYygxLDIsMyw0LDUsNiw3LDgsMTEsMTQsMjIsMjQpXQpgYGAKCl9fIFRoaXMgZGF0YSBzZXQgd2FzIG5vdCBub3JtYWxpemVkIHVzaW5nIDEwWCBkb3duc2FtcGxpbmcgYXMgdGhlIHNhbXBsZXMgaGF2ZSBsYXJnZSB2YXJpYWJpbGl0eSBpbiB0aGVpciBjZWxsIG51bWJlcnMuCgojIyBJbXBvcnRpbmcgY2VsbC1yYW5nZXIgZGF0YQpgYGB7cn0KY2VsbHJhbmdlcl9waXBlc3RhbmNlX3BhdGggPC0gIi9ob21lL3VidW50dS9SbmEtc2VxX0RhdGEtQW5hbHlzaXMvTG91aXNhX05lbHNvbl8xMFhfQW5hbHlzaXMvTE5fYWdnci9vdXRzL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4LyIKb3ZhcmlhbkNhbmNlciA8LSBSZWFkMTBYKGNlbGxyYW5nZXJfcGlwZXN0YW5jZV9wYXRoKQojYW5hbHlzaXNfcmVzdWx0cyA8LSBsb2FkX2NlbGxyYW5nZXJfYW5hbHlzaXNfcmVzdWx0cyhjZWxscmFuZ2VyX3BpcGVzdGFuY2VfcGF0aCkKYGBgCgoKYGBge3J9Cm92YXJpYW5DYW5jZXIKYGBgCgoKIyMgRXh0cmFjdGluZyBjZWxsIGluZm9ybWF0aW9uCmBgYHtyfQpTYW1wbGUxX2JhcmNvZGVzICA8LSBkYXRhLmZyYW1lKGJhcmNvZGU9Y29sbmFtZXMob3ZhcmlhbkNhbmNlcilbZ3JlcCgiMSIsIGNvbG5hbWVzKG92YXJpYW5DYW5jZXIpKV0sIFNhbXBsZT0iUGF0aWVudDEiKQpTYW1wbGUyX2JhcmNvZGVzICA8LSBkYXRhLmZyYW1lKGJhcmNvZGU9Y29sbmFtZXMob3ZhcmlhbkNhbmNlcilbZ3JlcCgiMiIsIGNvbG5hbWVzKG92YXJpYW5DYW5jZXIpKV0sIFNhbXBsZT0iUGF0aWVudDIiKQpTYW1wbGUzX2JhcmNvZGVzICA8LSBkYXRhLmZyYW1lKGJhcmNvZGU9Y29sbmFtZXMob3ZhcmlhbkNhbmNlcilbZ3JlcCgiMyIsIGNvbG5hbWVzKG92YXJpYW5DYW5jZXIpKV0sIFNhbXBsZT0iUGF0aWVudDMiKQpTYW1wbGU0X2JhcmNvZGVzICA8LSBkYXRhLmZyYW1lKGJhcmNvZGU9Y29sbmFtZXMob3ZhcmlhbkNhbmNlcilbZ3JlcCgiNCIsIGNvbG5hbWVzKG92YXJpYW5DYW5jZXIpKV0sIFNhbXBsZT0iUGF0aWVudDQiKQoKcHJpbnQocGFzdGUwKCdOdW1iZXIgb2YgU2FtcGxlMSBjZWxsczogJyxkaW0oU2FtcGxlMV9iYXJjb2RlcylbMV0pKQpwcmludChwYXN0ZTAoJ051bWJlciBvZiBTYW1wbGUyIGNlbGxzOiAnLGRpbShTYW1wbGUyX2JhcmNvZGVzKVsxXSkpCnByaW50KHBhc3RlMCgnTnVtYmVyIG9mIFNhbXBsZTMgY2VsbHM6ICcsZGltKFNhbXBsZTNfYmFyY29kZXMpWzFdKSkKcHJpbnQocGFzdGUwKCdOdW1iZXIgb2YgU2FtcGxlNCBjZWxsczogJyxkaW0oU2FtcGxlNF9iYXJjb2RlcylbMV0pKQpgYGAKCgpfX0NvbmZpcm1lZCB0aGlzIG51bWJlciB3aXRoIHRoZSBRQyBvdXRwdXQgcHJvZHVjZWQgYnkgY2VsbCByYW5nZXIuX18KCgpgYGB7cn0KYW5ub3RCYXJjb2RlIDwtIHJiaW5kKFNhbXBsZTFfYmFyY29kZXMsIFNhbXBsZTJfYmFyY29kZXMsIFNhbXBsZTNfYmFyY29kZXMsIFNhbXBsZTRfYmFyY29kZXMpCnJvd25hbWVzKGFubm90QmFyY29kZSkgPC0gYW5ub3RCYXJjb2RlJGJhcmNvZGUKaGVhZChhbm5vdEJhcmNvZGUpCmBgYAoKCkNyZWF0aW5nIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdCB1c2luZyBgc2NhdGVyYC4gV2Ugd291bGQgYmUgdXNpbmcgdGhlIGBleHByc2AgZnVuY3Rpb24gb2YgYGdibWAuIExhdGVyIG9uIHNjYXRlciBtaWdodCBhZGQgaXRzIG93biBmdW5jdGlvbiB0byBpbnB1dCAxMFggZGF0YSwgYnV0IGJlZm9yZSB0aGF0IHdlIHdpbGwgYmUgdXNpbmcgdGhpcyBtZXRob2QKYGBge3J9CmNkU2MgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzID0gbGlzdChjb3VudHMgPSBhcy5tYXRyaXgob3ZhcmlhbkNhbmNlcikpKQpjb2xEYXRhKGNkU2MpJGJhcmNvZGUgPC0gYW5ub3RCYXJjb2RlJGJhcmNvZGUKY29sRGF0YShjZFNjKSRTYW1wbGUgPC0gYW5ub3RCYXJjb2RlJFNhbXBsZQpyb3dEYXRhKGNkU2MpJHN5bWJvbCA8LSByb3duYW1lcyhvdmFyaWFuQ2FuY2VyKQpjZFNjIDwtIHNjYXRlcjo6Y2FsY3VsYXRlUUNNZXRyaWNzKGNkU2MpCmBgYApgYGB7cn0KY2RTYwpgYGAKCkhlcmUgd2UgdXNlIGxvZzItY291bnRzLXBlci1taWxsaW9uIHdpdGggYW4gb2Zmc2V0IG9mIDEgYXMgdGhlIGV4cHJzIHZhbHVlcy4gV2UgaGF2ZSB0byBub3RlIHRoYXQgdGhlIENQTSBmb3Igc2NhdGVyIGlzIGRpZmZlcmVudCB0aGFuIHRoZSByZWd1bGFyIENQTSBjYWxjdWxhdGlvbiBhcyB3ZSBhcmUgY29uc2lkZXJpbmcgdGhlIHN0ZXAtc2l6ZSBoZXJlLiAKClRoZSB2YWx1ZSBvZiB0aGUgbG9nLUNQTXMgaXMgZXhwbGFpbmVkIGJ5IGFkZGluZyBhIHByaW9yIGNvdW50IHRvIGF2b2lkIHVuZGVmaW5lZCB2YWx1ZXMgYWZ0ZXIgdGhlIGxvZy10cmFuc2Zvcm1hdGlvbiwgbXVsdGlwbHlpbmcgYnkgYSBtaWxsaW9uLCBhbmQgZGl2aWRpbmcgYnkgdGhlIG1lYW4gbGlicmFyeSBzaXplLiBUaGlzIHNpemUgZmFjdG9ycyBhcmUgdXNlZCB0byBkZWZpbmUgdGhlIGVmZmVjdGl2ZSBsaWJyYXJ5IHNpemVzLiBUaGlzIGlzIGRvbmUgYnkgc2NhbGluZyBhbGwgc2l6ZSBmYWN0b3JzIHN1Y2ggdGhhdCB0aGUgbWVhbiBzY2FsZWQgc2l6ZSBmYWN0b3IgaXMgZXF1YWwgdG8gdGhlIG1lYW4gc3VtIG9mIGNvdW50cyBhY3Jvc3MgYWxsIGZlYXR1cmVzLiBUaGUgZWZmZWN0aXZlIGxpYnJhcnkgc2l6ZXMgYXJlIHRoZW4gdXNlZCB0byBpbiB0aGUgZGVub21pbmF0b3Igb2YgdGhlIENQTSBjYWxjdWxhdGlvbi4gVGhlIHdheSB0aGF0IGBzY2F0ZXJgIGNhbGN1bGF0ZXMgdGhlIGxvZy1ub3JtYWxpemVkIGNvdW50cyBhcmUKCmBgYApsaWIuc2l6ZXMgPC0gY29sU3Vtcyhjb3VudHMoZXhhbXBsZV9zY2VzZXQpKQpsaWIuc2l6ZXMgPC0gbGliLnNpemVzL21lYW4obGliLnNpemVzKQpsb2cyKGNvdW50cyhleGFtcGxlX3NjZXNldClbMSxdL2xpYi5zaXplcysxKQpgYGAKV2Ugbm93IG5vcm1hbGl6ZSBmb3IgYWxsIHRoZSBjb3VudHMKYGBge3J9CmV4cHJzKGNkU2MpIDwtIGxvZzIoc2NhdGVyOjpjYWxjdWxhdGVDUE0oY2RTYywgdXNlX3NpemVfZmFjdG9ycyA9IFRSVUUpICsgMSkKYGBgCgpTYXZpbmcgRGF0YXNldApgYGB7cn0Kc2F2ZS5pbWFnZSgpCiMgc2F2ZS5pbWFnZSgnUENfMjAxODEwMDIuUkRhdGEnKQpgYGAKCiMgUUMgYW5hbHlzaXMKQXQgdGhlIHN0YXJ0IG9mIHRoZSBRQyBhbmFseXNpcyB3ZSBtYWtlIGRpZmZlcmVudCBwbG90cyB0byB2aXN1YWxpemUgdGhlIHN1bW1hcnkgc3RhdGlzdGljcy4KCiMjIENlbGwgUUMKTG93LXF1YWxpdHkgY2VsbHMgbmVlZCB0byBiZSBpZGVudGlmaWVkIGFuZCByZW1vdmVkIHRvIGVuc3VyZSB0aGF0IHRoZSB0ZWNobmljYWwgZWZmZWN0cyBkbyBub3QgZGlzdG9ydCBkb3duc3RyZWFtIGFuYWx5c2lzIHJlc3VsdHMuIFR3byBjb21tb24gbWVhc3VyZXMgb2YgY2VsbCBxdWFsaXR5IGFyZSB0aGUgX19saWJyYXJ5IHNpemVfXyBhbmQgdGhlIF9fbnVtYmVyIG9mIGV4cHJlc3NlZCBnZW5lc19fIGluIGVhY2ggbGlicmFyeS4gVGhlIGxpYnJhcnkgc2l6ZSBpcyBkZWZpbmVkIGFzIHRoZSB0b3RhbCBzdW0gb2YgY291bnRzIGFjcm9zcyBhbGwgZ2VuZXMuIENlbGxzIHdpdGggcmVsYXRpdmVseSBzbWFsbCBsaWJyYXJ5IHNpemVzIGFyZSBjb25zaWRlcmVkIHRvIGJlIG9mIGxvdyBxdWFsaXR5IGFzIHRoZSBSTkEgaGFzIG5vdCBiZWVuIGVmZmljaWVudGx5IGNhcHR1cmVkIChpLmUuLCBjb252ZXJ0ZWQgaW50byBjRE5BIGFuZCBhbXBsaWZpZWQpIGR1cmluZyBsaWJyYXJ5IHByZXBhcmF0aW9uLiBUaGUgbnVtYmVyIG9mIGV4cHJlc3NlZCBnZW5lcyBpbiBlYWNoIGNlbGwgaXMgZGVmaW5lZCBhcyB0aGUgbnVtYmVyIG9mIGdlbmVzIHdpdGggbm9uLXplcm8gY291bnRzIGZvciB0aGF0IGNlbGwuIEFueSBjZWxsIHdpdGggdmVyeSBmZXcgZXhwcmVzc2VkIGdlbmVzIGlzIGxpa2VseSB0byBiZSBvZiBwb29yIHF1YWxpdHkgYXMgdGhlIGRpdmVyc2UgdHJhbnNjcmlwdCBwb3B1bGF0aW9uIGhhcyBub3QgYmVlbiBzdWNjZXNzZnVsbHkgY2FwdHVyZWQuIAoKCiMjIyBSZWFkcyBtYXBwaW5nIHdpdGggbWl0b2Nob25kcmlhbCByZWFkcwpXZSBpZGVudGlmeSB0aGUgcm93cyBjb3JyZXNwb25kaW5nIHRvIG1pdG9jaG9uZHJpYWwgZ2VuZXMuIEFub3RoZXIgaW1wb3J0YW50IG1lYXN1cmUgb2YgcXVhbGl0eSBpcyB0aGUgcHJvcG9ydGlvbiBvZiByZWFkcyBtYXBwZWQgdG8gZ2VuZXMgaW4gdGhlIG1pdG9jaG9uZHJpYWwgZ2Vub21lLiBIaWdoIHByb3BvcnRpb25zIGFyZSBpbmRpY2F0aXZlIG9mIHBvb3ItcXVhbGl0eSBjZWxscyAoW0lsaWNpYyBldCBhbC4sIDIwMTZdKGh0dHBzOi8vZjEwMDByZXNlYXJjaC5jb20vYXJ0aWNsZXMvNS0yMTIyL3YyI3JlZi0xNCk7IFtJc2xhbSBldCBhbC4sIDIwMTRdKGh0dHBzOi8vZjEwMDByZXNlYXJjaC5jb20vYXJ0aWNsZXMvNS0yMTIyL3YyI3JlZi0xNikpLCBwb3NzaWJseSBiZWNhdXNlIG9mIGluY3JlYXNlZCBhcG9wdG9zaXMgYW5kL29yIGxvc3Mgb2YgY3l0b3BsYXNtaWMgUk5BIGZyb20gbHlzZWQgY2VsbHMuIAoKUmVhZHMgbWFwcGluZyB3aXRoIG1pdG9jaG9uZHJpYWwgcmVhZHMKCldlIGlkZW50aWZ5IHRoZSByb3dzIGNvcnJlc3BvbmRpbmcgdG8gbWl0b2Nob25kcmlhbCBnZW5lcy4KCmBgYHtyfQpsaWJyYXJ5KGJpb21hUnQpCm15LmlkcyA8LSBnc3ViKCdcXC4uKicsJycscm93bmFtZXMoY2RTYykpCmVuc2VtYmwgPSB1c2VFbnNlbWJsKGJpb21hcnQ9ImVuc2VtYmwiLCBkYXRhc2V0PSJoc2FwaWVuc19nZW5lX2Vuc2VtYmwiKQpjaHJOYW1lIDwtIGdldEJNKGF0dHJpYnV0ZXM9YygnZW5zZW1ibF9nZW5lX2lkJywnaGduY19zeW1ib2wnLCdjaHJvbW9zb21lX25hbWUnKSwgZmlsdGVycyA9ICdoZ25jX3N5bWJvbCcsIHZhbHVlcyA9bXkuaWRzLCBtYXJ0ID0gZW5zZW1ibCkKYGBgCgpgYGB7cn0KY2hyTmFtZSA8LSBjaHJOYW1lW21hdGNoKG15LmlkcywgY2hyTmFtZSRoZ25jX3N5bWJvbCksXQppcy5taXRvIDwtIGNock5hbWUkY2hyb21vc29tZV9uYW1lID09ICJNVCIgJiAhaXMubmEoY2hyTmFtZSRjaHJvbW9zb21lX25hbWUpCnN1bShpcy5taXRvKQpgYGAKYGBge3J9CmNkU2MgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKGNkU2MsIGZlYXR1cmVfY29udHJvbHM9bGlzdChNdD1pcy5taXRvKSkKYGBgCgpQbG90dGluZyB0aGUgaGlzdG9ncmFtcyBvZiBteSBRQyBlbnRyaWVzLgpgYGB7ciBmaXRRQ19NdCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CnBhcihtZnJvdz1jKDIsMikpCmhpc3QoKGNkU2MkdG90YWxfY291bnRzKS8xZTYsIHhsYWI9IkxpYnJhcnkgc2l6ZXMgKG1pbGxpb25zKSIsIG1haW49Ikhpc3RvZ3JhbSBvZiBMaWJyYXJ5IHNpemUiLAogICAgIGJyZWFrcz0xMDAsIGNvbD0iZ3JleTgwIiwgeWxhYj0iTnVtYmVyIG9mIGNlbGxzIikKCmhpc3QoY2RTYyR0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMsIHhsYWI9Ik51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMiLCBtYWluPSJIaXN0b2dyYW0gb2YgTm8uIG9mIEZlYXR1cmVzIiwKICAgICBicmVha3M9MTAwLCBjb2w9ImdyZXk4MCIsIHlsYWI9Ik51bWJlciBvZiBjZWxscyIpCgpoaXN0KGNkU2MkcGN0X2NvdW50c19NdCwgeGxhYj0iTWl0b2Nob25kcmlhbCBwcm9wb3J0aW9uICglKSIsCiAgICAgeWxhYj0iTnVtYmVyIG9mIGNlbGxzIiwgYnJlYWtzPTEwMCwgbWFpbj0iSGlzdG9ncmFtIG9mIE1pY3RvY2hvbmRyaWEgJSIsIGNvbD0iZ3JleTgwIikKYGBgCgpUaGUgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBtaXRvY2hvbmRyaWFsIHJlYWQgcHJvcG9ydGlvbiBpcyByZWFzb25hYmx5IGdvb2QuIEFzIGFueXRoaW5nIGJlbG93IDEwJSBpcyB2ZXJ5IGdvb2QgYW5kIGhlcmUgdGhlIG1lYW4gaXMgYXJvdW5kIDclLiBIb3dldmVyLCB0aGVyZSBhcmUgc29tZSBvdXRsaWVyIGNlbGxzIGV4cHJlc3NpbmcgdmVyeSBoaWdoIG1pdG9jb25kcmlhbCBnZW5lcy4gSSBhbSBnb2luZyB0byBmaWx0ZXIgdGhvc2UgZ2VuZXMgb3V0LgoKYGBge3J9CnN1bW1hcnkoY2RTYyRwY3RfY291bnRzX010KQpgYGAKCgpJdCBpcyBhbHNvIHZhbHVhYmxlIHRvIGV4YW1pbmUgaG93IHRoZSBRQyBtZXRyaWNzIGJlaGF2ZSB3aXRoIHJlc3BlY3QgdG8gZWFjaCBvdGhlci4gR2VuZXJhbGx5LCB0aGV5IHdpbGwgYmUgaW4gcm91Z2ggYWdyZWVtZW50LCBpLmUuLCBjZWxscyB3aXRoIGxvdyB0b3RhbCBjb3VudHMgd2lsbCBhbHNvIGhhdmUgbG93IG51bWJlcnMgb2YgZXhwcmVzc2VkIGZlYXR1cmVzIGFuZCBoaWdoIG1pdG9jaG9uZHJpYWwgcHJvcG9ydGlvbnMuCgpgYGB7cn0KcGFyKG1mcm93PWMoMSwzKSkKcGxvdChjZFNjJHRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cywgY2RTYyR0b3RhbF9jb3VudHMvMWU2LCB4bGFiPSJOdW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzIiwKICAgIHlsYWI9IkxpYnJhcnkgc2l6ZSAobWlsbGlvbnMpIikKcGxvdChjZFNjJHRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cywgY2RTYyRwY3RfY291bnRzX010LCB4bGFiPSJOdW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzIiwKICAgIHlsYWI9Ik1pdG9jaG9uZHJpYWwgcHJvcG9ydGlvbiAoJSkiKQpwbG90KGNkU2MkdG90YWxfY291bnRzLzFlNiwgY2RTYyRwY3RfY291bnRzX010LCB4bGFiPSJUb3RhbCBjb3VudHMgKGluIG1pbGxpb25zKSIsCiAgICB5bGFiPSJNaXRvY2hvbmRyaWFsIHByb3BvcnRpb24gKCUpIikKCmBgYAoKCkFsc28gaG93IHRoZSBjb3VudHMgYXJlIGRpc3RyaWJ1dGVkIGFjcm9zcyB0aGUgY2VsbHMuLgpgYGB7cn0KcGxvdFJvd0RhdGEoY2RTYywgeCA9ICJuX2NlbGxzX2J5X2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfY291bnRzIiwgY29sb3VyX2J5ID0gImlzX2ZlYXR1cmVfY29udHJvbF9NdCIpCmBgYApJbiB0aGUgZmlndXJlIGFib3ZlIHdlIHNlZSB0aGF0IHNvbWUgb2YgdGhlIG1pdG9jaG9uZHJpYWwgZ2VuZXMgKG1hcmtlZCBpbiBvcmFuZ2UpIGF0IHRoZSB0b3AgcmlnaHQgY29ybmVyIG9mIHRoZSBmaWd1cmUgaGFzIHZlcnkgaGlnaCBsb2cxMF90b3RhbF9jb3VudHMgYW5kIGFsc28gYXJlIGV4cHJlc3NlZCBpbiBhIGxhcmdlIG51bWJlciBvZiBjZWxscyAoYWxtb3N0IGluIGFsbCBjZWxscykuIFRoaXMgaXMgbm90IHZlcnkgc3VycHJpc2luZyBhcyBzb21lIG9mIHRoZSBtaXRvY2hvbmRyaWFsIGdlbmVzIGFyZSBpbmRlZWQgZXhwcmVzc2VkIGFjcm9zcyBhbGwgdGhlIGNlbGxzIGFzIHRoZXkgYXJlIHByb2R1Y2luZyBlbmVyZ3kuIFRoZSB3b3JyeWluZyBwYXJ0IHdvdWxkIGJlIGlmIHRoZXNlIGdlbmVzIHRha2VzIG1ham9yaXR5IG9mIGEgY2VsbCdzIHJlYWQgKG1vcmUgdGhhbiA4MCUpLiBUaGVuIEkgd291bGQgcmVtb3ZlIHRob3NlIGNlbGxzLgoKYGBge3J9CnBsb3RSb3dEYXRhKGNkU2MsIHggPSAibl9jZWxsc19ieV9jb3VudHMiLCB5ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIGNvbG91cl9ieSA9ICJwY3RfZHJvcG91dF9ieV9jb3VudHMiKQpgYGAKCkFzIGV4cGVjdGVkLCBjZWxscyB3aXRoIGxvdyBudW1iZXIgb2YgcmVhZHMgaGF2ZSBoaWdoZXIgZHJvcG91dCBjb3VudHMuCgpgYGB7cn0KcGxvdENvbERhdGEoY2RTYywgeCA9ICJsb2cxMF90b3RhbF9jb3VudHMiLCB5ID0gImxvZzEwX3RvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsIGNvbG91cl9ieSA9ICJwY3RfY291bnRzX010IikKYGBgCkFnYWluIGNlbGxzIHdpdGggbG93ZXIgcmVhZCBjb3VudHMgYW5kIGxvd2VyIHRvdGFsIGZlYXR1cmUgY291bnRzIGhhdmUgdmVyeSBoaWdoIHByb3BvcnRpb24gb2YgbWl0Y2hvbmRyaWFsIHJlYWRzLCBjbGVhcmx5IGluZGljYXRpbmcgdGhhdCB0aGVzZSBjZWxscyBlaXRoZXIgc3RhcnRlZCBhcG9wdG9zaXMgb3IgYnJva2VuIGFicnVwdGx5LiBXZSBjYW4gc2FmZWx5IHJlbW92ZSB0aGVzZSBjZWxscyBpbiBvdXIgUUMgZmlsdGVyaW5nLiAKClRvIHNlZSB0aGUgUUMgbWV0cmljcyByZWxhdGVkIHdpdGggY2VsbHMsIAoKYGBge3J9Cm5hbWVzKGNvbERhdGEoY2RTYykpCmBgYAoKCmFuZCB0byBzZWUgdGhlIFFDIG1ldHJpY3MgcmVsYXRlZCB3aXRoIGdlbmVzLApgYGB7cn0KbmFtZXMocm93RGF0YShjZFNjKSkKYGBgCgoKYGBge3J9CnBsb3RDb2xEYXRhKGNkU2MsIHggPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgeT0ibG9nMTBfdG90YWxfY291bnRzIiwgc2l6ZV9ieSA9InRvdGFsX2NvdW50c19NdCIsIGNvbG91cl9ieSAgPSAicGN0X2NvdW50c19NdCIpCmBgYApBbGwgdGhlIGFib3ZlIGZpZ3VyZXMgY2xlYXJseSBzaG93IHRoYXQgdGhlIGhpZ2ggcHJvcG9ydGlvbnMgb2YgcmVhZHMgbWFwcGVkIHRvIGdlbmVzIGluIHRoZSBtaXRvY2hvbmRyaWFsIGdlbm9tZSBpcyBpbmRpY2F0aXZlIG9mIHBvb3IgcXVhbGl0eSBjZWxscyAoW0lsaWNpYyBldCBhbC4sIDIwMTZdKGh0dHBzOi8vZjEwMDByZXNlYXJjaC5jb20vYXJ0aWNsZXMvNS0yMTIyL3YyI3JlZi0xNCk7IFtJc2xhbSBldCBhbC4sIDIwMTRdKGh0dHBzOi8vZjEwMDByZXNlYXJjaC5jb20vYXJ0aWNsZXMvNS0yMTIyL3YyI3JlZi0xNikpLCBwb3NzaWJseSBiZWNhdXNlIG9mIGluY3JlYXNlZCBhcG9wdG9zaXMgYW5kL29yIGxvc3Mgb2YgY3l0b3BsYXNtaWMgUk5BIGZyb20gbHlzZWQgY2VsbHMuIEkgd291bGQgYmUgZmlsdGVyaW5nIHRob3NlIGNlbGxzIG91dC4gIAoKVGhlIGZvbGxvd2luZyBmaWd1cmUgZ2l2ZXMgYW4gaWRlYSBvZiBob3cgbWFueSBnZW5lcyBhcmUgZXhwcmVzc2VkIGluIGhvdyBtYW55IGNlbGxzLgoKYGBge3J9CnBsb3RFeHByc0ZyZXFWc01lYW4oY2RTYykKYGBgClRoZSBnZW5lcyBsb29rcyBnb29kIGFzIHRoZXkgYXJlIG5vdCB5ZXQgZmlsdGVyZWQuCgoKTm93LCBwaWNraW5nIHVwIGEgdGhyZXNob2xkIGZvciBkaWZmZXJlbnQgbWV0cmljZXMgaW4gY2VsbCBmaWx0ZXJpbmcgaXMgbm90IHN0cmFpZ2h0Zm9yd2FyZCBhcyB0aGVpciBhYnNvbHV0ZSB2YWx1ZXMgZGVwZW5kIG9uIHRoZSBwcm90b2NvbCBhbmQgYmlvbG9naWNhbCBzeXN0ZW0uIEZvciBleGFtcGxlLCBzZXF1ZW5jaW5nIHRvIGdyZWF0ZXIgZGVwdGggd2lsbCBsZWFkIHRvIG1vcmUgcmVhZHMsIHJlZ2FyZGxlc3Mgb2YgdGhlIHF1YWxpdHkgb2YgdGhlIGNlbGxzLiBUbyBvYnRhaW4gYW4gYWRhcHRpdmUgdGhyZXNob2xkLCB3ZSBhc3N1bWUgdGhhdCBtb3N0IG9mIHRoZSBkYXRhc2V0IGNvbnNpc3RzIG9mIGhpZ2gtcXVhbGl0eSBjZWxscy4gVG8gZmFjaWxpdGF0ZSBpbiBwaWNraW5nIGEgdGhyZXNob2xkIGZvciBvdXIgY2VsbCBjdXRvZmYgd2Ugbm93IHBsb3QgY291cGxlIG9mIGRlbnNpdGlpZXMuCmBgYHtyfQpjdXRfb2ZmX3JlYWRzIDwtIG1lZGlhbihjZFNjJGxvZzEwX3RvdGFsX2NvdW50cykgLSAzKm1hZChjZFNjJGxvZzEwX3RvdGFsX2NvdW50cykKZGYgPC0gZGF0YS5mcmFtZSh4PWNkU2MkbG9nMTBfdG90YWxfY291bnRzLCBTYW1wbGUgPSBjZFNjJFNhbXBsZSkKcGxvdF9yZWFkcyA8LSBnZ3Bsb3QoZGYsCiAgICAgICBhZXMoeCA9IHgsIGZpbGwgPSBhcy5mYWN0b3IoU2FtcGxlKSkpICsgCiAgICAgICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsKICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGN1dF9vZmZfcmVhZHMsIGNvbG91cj0iZ3JleSIsIGxpbmV0eXBlID0gImxvbmdkYXNoIikgKwogICAgICAgbGFicyh4ID0gZXhwcmVzc2lvbignbG9nJ1sxMF0qJyhMaWJyYXJ5IFNpemUpJyksIHRpdGxlID0gIlRvdGFsIHJlYWRzIGRlbnNpdHkiLCBmaWxsID0gIlNhbXBsZSIpICsgCiAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSsKICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpCgoKY3V0X29mZl9tUk5BIDwtIG1lZGlhbihjZFNjJGxvZzEwX3RvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cykgLSAzKm1hZChjZFNjJGxvZzEwX3RvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cykKZGYgPC0gZGF0YS5mcmFtZSh4PWNkU2MkbG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzLCBTYW1wbGUgPSBjZFNjJFNhbXBsZSkKcGxvdF9tUk5BIDwtIGdncGxvdChkZiwKICAgICAgIGFlcyh4ID0geCwgZmlsbCA9IGFzLmZhY3RvcihTYW1wbGUpKSkgKyAKICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gY3V0X29mZl9tUk5BLCBjb2xvdXI9ImdyZXkiLCBsaW5ldHlwZSA9ICJsb25nZGFzaCIpICsKICAgICAgIGxhYnMoeCA9IGV4cHJlc3Npb24oJ2xvZydbMTBdKicoTnVtYmVyIG9mIGV4cHJlc3NlZCBnZW5lcyknKSwgdGl0bGUgPSAiVG90YWwgZ2VuZXMgZXhwcmVzc2VkIiwgZmlsbCA9ICJTYW1wbGUiKSArIAogICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkrCiAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKQoKCmN1dF9vZmZfTVQgPC0gbWVkaWFuKGNkU2MkcGN0X2NvdW50c19NdCkgKyAzKm1hZChjZFNjJHBjdF9jb3VudHNfTXQpCmRmIDwtIGRhdGEuZnJhbWUoeD1jZFNjJHBjdF9jb3VudHNfTXQsIFNhbXBsZSA9IGNkU2MkU2FtcGxlKQpwbG90X01UIDwtIGdncGxvdChkZiwKICAgICAgIGFlcyh4ID0geCwgZmlsbCA9IGFzLmZhY3RvcihTYW1wbGUpKSkgKyAKICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gY3V0X29mZl9NVCwgY29sb3VyPSJncmV5IiwgbGluZXR5cGUgPSAibG9uZ2Rhc2giKSArCiAgICAgICBsYWJzKHggPSBleHByZXNzaW9uKCdQY3Qgb2YgTVQgRXhwcmVzc2lvbicpLCB0aXRsZSA9ICJNaXRvY2hvbmRyaWFsIEV4cHJlc3Npb24iLCBmaWxsID0gIlNhbXBsZSIpICsgCiAgICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSsKICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpCgpgYGAKCmBgYHtyIGZpZ0Rpc3QsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSA5fQptdWx0aXBsb3QocGxvdF9yZWFkcywgcGxvdF9NVCwgcGxvdF9tUk5BLCAgY29scz0yKQpgYGAKCmBgYHtyIGZpZ0luZERpc3QsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSA5fQpkZiA8LSBkYXRhLmZyYW1lKHg9Y2RTYyRsb2cxMF90b3RhbF9jb3VudHMpCmFzX3RpYmJsZShkZikgJT4lCiAgZHBseXI6Om11dGF0ZSgiU2FtcGxlIiA9IGNkU2MkU2FtcGxlKSAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0geCwgZmlsbCA9IGFzLmZhY3RvcihTYW1wbGUpKSkgKyAKICAgICAgIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gY3V0X29mZl9yZWFkcywgY29sb3VyPSJncmV5IiwgbGluZXR5cGUgPSAibG9uZ2Rhc2giKSArCiAgICAgICBsYWJzKHggPSBleHByZXNzaW9uKCdsb2cnWzEwXSonKExpYnJhcnkgU2l6ZSknKSwgdGl0bGUgPSAiVG90YWwgcmVhZHMgZGVuc2l0eSIsIGZpbGwgPSAiU2FtcGxlIikgKyAKICAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgKyBmYWNldF93cmFwKH5TYW1wbGUsIG5yb3c9MixuY29sPTIpCmBgYAoKClRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHJlYWRzLCBudW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzIGFuZCB0aGUgcGVyY2VudCBvZiBNVCBleHByZXNzaW9uLiBUaGUgc2hhZGVkIGxpbmUgcmVwcmVzZW50cyB0aGUgdGhyZXNob2xkIHdoaWNoIGlzIHRoZSAzIE1lZGlhbiBBYnNvbHV0ZSBEZXZpYXRpb24gKE1BRCkuIEZvciB0aGUgdG9wIHR3byBmaWd1cmVzLCBjZWxscyBvbiB0aGUgbGVmdCBvZiB0aGlzIHRocmVzaG9sZCB3b3VsZCBiZSBmaWx0ZXJlZCBvdXQgYW5kIGZvciB0aGUgYm90dG9tIGZpZ3VyZSwgY2VsbHMgb24gdGhlIHJpZ2h0IG9mIHRoaXMgZmlndXJlIHdvdWxkIGJlIGZpbHRlcmVkIG91dC4KCldlIHNlZSB0aGF0IGluIHRoZSBsaWJyYXJ5IHNpemUgcGxvdCBhbmQgYWxzbyB0aGUgcGxvdCB3aXRoIG51bWJlciBvZiBleHByZXNzZWQgZmVhdHVyZXMsIHRoZXJlIGFyZSBhIGJpb21vZGFsIGRpc3RyaWJ1dGlvbi4gSW4gdGhlIHByZXZpb3VzIGNlbGxyYW5nZXIgdmVyc2lvbnMgd2UgZ2VuZXJhbGx5IGRvIG5vdCBvYnNlcnZlIHRoaXMuIEJ1dCB3aXRoIG5ldyBjZWxscmFuZ2VyIGl0IGNhbiBjYXB0dXJlIGNlbGxzIHdpdGggbG93ZXIgUk5BIGNvbnRlbnRzLiBEdWUgdG8gdGhpcyB3ZSBhcmUgbm93IGdldHRpbmcgY2VsbHMgd2l0aCBsb3dlciByZWFkIGNvdW50cy4gCgpXZSByZW1vdmUgY2VsbHMgd2l0aCBsb2ctbGlicmFyeSBzaXplcyB0aGF0IGFyZSBtb3JlIHRoYW4gNCBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9ucyAoTUFEcykgYmVsb3cgdGhlIG1lZGlhbiBsb2ctbGlicmFyeSBzaXplLiAoQSBsb2ctdHJhbnNmb3JtYXRpb24gaW1wcm92ZXMgcmVzb2x1dGlvbiBhdCBzbWFsbCB2YWx1ZXMsIGVzcGVjaWFsbHkgd2hlbiB0aGUgTUFEIG9mIHRoZSByYXcgdmFsdWVzIGlzIGNvbXBhcmFibGUgdG8gb3IgZ3JlYXRlciB0aGFuIHRoZSBtZWRpYW4pLiBXZSBhbHNvIHJlbW92ZSBjZWxscyB3aGVyZSB0aGUgbG9nLXRyYW5zZm9ybWVkIG51bWJlciBvZiBleHByZXNzZWQgZ2VuZXMgaXMgNCBNQURzIGJlbG93IHRoZSBtZWRpYW4uIFNpbWlsYXJseSwgd2UgZHJvcCB0aGUgY2VsbHMgaGF2aW5nIHBjdCBvZiBNVCBleHByZXNzaW9uIGJlbG93IDMgTUFELgoKQWx0aG91Z2ggZnJvbSB0aGUgY2VsbHJhbmdlciBvdXRwdXQgaXQgbG9va2VkIHRoYXQgdGhlIGNlbGxzIGhhdmUgdmVyeSBkaWZmZXJlbnQgdG90YWwgY291bnRzLCBidXQgYWZ0ZXIgcmVtb3ZpbmcgdGhlIGV4dHJlbWUgb3V0bGllciBjZWxscyBpdCBsb29rcyBsaWtlIHRoZXkgaGF2ZSBxdWl0ZSBuaWNlIHBhY2tlZCBkaXN0cmlidXRpb24uCgpIb3dldmVyLCBpbiB0aGUgRyBzYW1wbGVzIGhpZ2hlciBudW1iZXIgb2YgY2VsbHMgaGF2ZSBoaWdoZXIgcmVhZCBjb3VudHMgYXMgY2FuIGJlIHNlZW4gZnJvbSB0aGUgaW5kaXZpZHVhbCBkZW5zaXR5LiBCdXQgdGhhdCBkb2VzIG5vdCBpbXBhY3QgdGhlIGN1dG9mZiB2YWx1ZS4gCkJlbG93IHdlIGNoZWNrIHRoZSBjdXRvZmYgdmFsdWVzIHRvIHZhbGlkYXRlIG91ciBmaWx0ZXJpbmcuCgpgYGB7cn0KcHJpbnQocGFzdGUwKCdSZWFkIGNvdW50IEN1dG9mZjogJywxMF5jdXRfb2ZmX3JlYWRzKSkKcHJpbnQocGFzdGUwKCdHZW5lcyBjb3VudCBDdXRvZmY6ICcsMTBeY3V0X29mZl9tUk5BKSkKcHJpbnQocGFzdGUwKCdNVCBwZXJjZW50IGNvdW50IEN1dG9mZjogJyxjdXRfb2ZmX01UKSkKYGBgCgoKCldlIG5vdyBjYWxjdWxhdGUgdGhlIGNlbGxzIGFuZCBnZW5lcyB0aGF0IGFyZSBkZWVtZWQgdG8gYmUgb3V0bGllcnMgZHVlIHRvIGZhbGxpbmcgYmVsb3cgMyBNQUQuCgpDZWxscyBoYXZpbmcgcmVhZCBjb3VudHMgYmVsb3cgMTI4OSAoQnlMaWJTaXplKSBhbmQgbnVtYmVyIG9mIGdlbmVzIGV4cHJlc3NlZCBiZWxvdyA4NjQgKEJ5RmVhdHVyZSkgd291bGQgYmUgZHJvcHBlZCBhbmQgaWYgY2VsbHMgYXJlIGhhdmluZyBtb3JlIHRoYW4gMTQlIG9mIG1pdG9jaG9uZHJpYWwgcmVhZHMsIEkgd291bGQgZHJvcCB0aGUgY2VsbHMuCgpgYGB7cn0KbGlic2l6ZS5kcm9wIDwtIGlzT3V0bGllcihjZFNjJHRvdGFsX2NvdW50cywgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKZmVhdHVyZS5kcm9wIDwtIGlzT3V0bGllcihjZFNjJHRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cywgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKbWl0by5kcm9wIDwtIGlzT3V0bGllcihjZFNjJHBjdF9jb3VudHNfTXQsIG5tYWRzPTMsIHR5cGU9ImhpZ2hlciIpCmBgYAoKQmVsb3cgYXJlIHRoZSBzdGF0cyBmb3IgdGhlIGNlbGxzIHRoYXQgd291bGQgYmUgZHJvcHBlZApgYGB7cn0KZGF0YS5mcmFtZShCeUxpYlNpemU9c3VtKGxpYnNpemUuZHJvcCksIEJ5RmVhdHVyZT1zdW0oZmVhdHVyZS5kcm9wKSwgCiAgICAgICAgICAgQnlNaXRvPXN1bShtaXRvLmRyb3ApKQpgYGAKClNvLCBiYXNlZCBvbiBsaWJyYXJ5IHNpemUsIDgyNSBjZWxscyB3b3VsZCBiZSBkcm9wcGVkIGFuZCBiYXNlZCBvbiBudW1iZXIgb2YgZmVhdHVyZXMgZXhwcmVzc2VkLCAxMjEwIGNlbGxzIHdvdWxkIGJlIGRyb3BwZWQgYW5kIGZvciBNaXRvY2hvbmRyaWFsIHByb3BvcnRpb24gb2YgcmVhZHMgNTE3IGNlbGxzIHdvdWxkIGJlIGRyb3BwZWQuIFBsZWFzZSBub3RlIHRoYXQgYWxsIHRoZXNlIGNlbGxzIGFyZSByZW1vdmVkIGZyb20gdGhlIHRvdGFsIHBvcHVsYXRpb24gYW5kIGFsc28gbWFueSBvZiB0aGVzZSBjZWxscyB3b3VsZCBiZSBvdmVybGFwcGluZyBhY3Jvc3MgY29uZGl0aW9ucy4KCgpHZW5lcmF0aW5nLCB0aGUgYHNjYXRlcmAgb2JqZWN0IHdpdGggZmlsdGVyZWQgcHJvZmlsZS4KYGBge3J9CmNkU2NGaWx0IDwtIGNkU2NbLCEobGlic2l6ZS5kcm9wIHwgZmVhdHVyZS5kcm9wIHwgbWl0by5kcm9wKV0KY2RTY0ZpbHQgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKGNkU2NGaWx0KQpgYGAKCkJlZm9yZSBmaWx0ZXJpbmc6CmBgYHtyfQpjZFNjCmBgYAoKQWZ0ZXIgZmlsdGVyaW5nOgpgYGB7cn0KY2RTY0ZpbHQKYGBgCgoKQmVmb3JlIGZpbHRlcmluZyBudW1iZXIgb2YgY2VsbHMgd2VyZSwKYGBge3J9CnByaW50KHBhc3RlMCgiQ2VsbHMgYmVmb3JlIGZpbHRlcmluZzogIixkaW0oY2RTYylbMl0pKQpgYGAKCgoKQWZ0ZXIgZmlsdGVyaW5nIHJlbWFpbmluZyBjZWxscyBhcmU6CmBgYHtyfQpwcmludChwYXN0ZTAoIkNlbGxzIHJlbWFpbmluZyBhZnRlciBmaWx0ZXJpbmc6ICIsZGltKGNkU2NGaWx0KVsyXSkpCmBgYAoKRm9yIHRoZSBmb3VyIHNhbXBsZXMsIG51bWJlciBvZiBjZWxscyB0aGF0IGFyZSByZW1haW5pbmcgYXJlOgpgYGB7cn0KcHJpbnQobGV2ZWxzKGFzLmZhY3Rvcihjb2xEYXRhKGNkU2MpJFNhbXBsZSkpKQpwcmludChwYXN0ZTAoJ0JlZm9yZSBGaWx0ZXJpbmc6ICcsIHRhYmxlKGNvbERhdGEoY2RTYykkU2FtcGxlKSkpCnByaW50KHBhc3RlMCgnQWZ0ZXIgRmlsdGVyaW5nOiAnLCB0YWJsZShjb2xEYXRhKGNkU2NGaWx0KSRTYW1wbGUpKSkKYGBgCgpTbywgdGhlIGNlbGxzIHRoYXQgYXJlIGJlaW5nIGZpbHRlcmVkIG91dCBhcmUga2luZCBvZiBldmVubHkgZGlzdHJpYnV0ZWQgYWNyb3NzIHRoZSBzYW1wbGVzLiBUaGlzIGlzIGtpbmQgb2YgcmVhc3N1cmluZyB0aGF0IHRoZXJlIHdhcyBub3QgYSBwb2ludCBmYWlsdXJlIHdoZXJlIG9uZSBzYW1wbGUgdG90YWxseSBmYWlsZWQuIEFmdGVyIGZpbHRlcmluZyB3ZSBoYXZlIGluIHRvdGFsIDIxNjA5IGNlbGxzIHJlbWFpbmluZyBmb3IgZG93bnN0cmVhbSBhbmFseXNpcyB3aGljaCBpcyBhIHZlcnkgcmVhc29uYWJsZSBudW1iZXIuCgpOb3csIEkgd2lsbCBhZ2FpbiBydW4gY291cGxlIG9mIFFDcyB0byBzZWUgd2hldGhlciBmdXJ0aGVyIGZpbHRlcmluZyBpcyByZXF1aXJlZC4KClJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRvdGFsIGNvdW50cyBhbmQgdG90YWwgZmVhdHVyZXMgYmVmb3JlIGZpbHRlcmluZzoKYGBge3J9CnAgPC0gcGxvdENvbERhdGEoY2RTYywgeD0ibG9nMTBfdG90YWxfY291bnRzIix5PSJ0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMiLCBjb2xvdXJfYnkgPSAiU2FtcGxlIikgKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKcApgYGAKCkFmdGVyIGZpbHRlcmluZzoKYGBge3J9CnAgPC0gcCA8LSBwbG90Q29sRGF0YShjZFNjRmlsdCwgeD0ibG9nMTBfdG90YWxfY291bnRzIix5PSJ0b3RhbF9mZWF0dXJlc19ieV9jb3VudHMiLCBjb2xvdXJfYnkgPSAiU2FtcGxlIikgKwogICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkKcApgYGAKV2UgY291bGQgc2VlIHRoYXQgc29tZSBvZiB0aGUgb3V0bGllciBjZWxscyBoYXZlIGJlZW4gZHJvcHBlZC4KCkJlZm9yZSBmaWx0ZXJpbmc6CmBgYHtyfQpwbG90Q29sRGF0YShjZFNjLCB4ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9jb3VudHNfTXQiKQpgYGAKCkFmdGVyIGZpbHRlcmluZzoKYGBge3J9CnBsb3RDb2xEYXRhKGNkU2NGaWx0LCB4ID0gImxvZzEwX3RvdGFsX2NvdW50cyIsIHkgPSAibG9nMTBfdG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9jb3VudHNfTXQiKQpgYGAKV2Ugc2VlIHRoYXQgdGhlIGdvb2QgY2VsbHMgYXJlIGtlcHQgd2hpbGUgY2VsbHMgd2l0aCB2ZXJ5IGhpZ2ggTXQgcmVhZHMgYXJlIGJlaW5nIHJlbW92ZWQuCgpOb3cgdG8gc2VlIGFib3V0IHRoZSBoaWdoZXIgcmVhZCBkZXB0aCBjZWxscyBJIHBsb3QgdmlvbGluIHBsb3RzLiBUaGlzIHdvdWxkIGdpdmUgbWUgYW4gaW5kaWNhdGlvbiBhcyB3aGV0aGVyIHRoZXJlIGlzIGFueSBvYnZpb3VzIG91dGxpZXJzLiBJIHdpbGwgYmUgcGxvdHRpbmcgdGhlIGNlbGxzIGZvciBgVG90YWxDb3VudHNgIGFuZCBgVG90YWxGZWF0dXJlc01pdG9jaG9uZHJpYWAuCgpgYGB7ciBmaWdWaW9saW4sIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoID0gOX0KZGYgPC0gZGF0YS5mcmFtZShDZWxsPWNvbG5hbWVzKGNkU2NGaWx0KSwgQ2VsbFR5cGU9Y2RTY0ZpbHQkU2FtcGxlLCB0b3RhbEZlYXR1cmVzPWNkU2NGaWx0JHRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cywgdG90YWxDb3VudD1jZFNjRmlsdCR0b3RhbF9jb3VudHMsIFBjdFRvdGFsQ291bnRNdD1jZFNjRmlsdCRwY3RfY291bnRzX010LCBTYW1wbGU9Y2RTY0ZpbHQkU2FtcGxlKQpwMSA8LSBnZ3Bsb3QoZGYsIGFlcyhmYWN0b3IoQ2VsbFR5cGUpLHRvdGFsQ291bnQsY29sb3VyPVNhbXBsZSkpCnAxIDwtIHAxICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCnAyIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksUGN0VG90YWxDb3VudE10LGNvbG91cj1TYW1wbGUpKQpwMiA8LSBwMiArIGdlb21fdmlvbGluKCkgKyBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMSkgKyB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSkpCgpwMyA8LSBnZ3Bsb3QoZGYsIGFlcyhmYWN0b3IoQ2VsbFR5cGUpLHRvdGFsRmVhdHVyZXMsY29sb3VyPVNhbXBsZSkpCnAzIDwtIHAzICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCgptdWx0aXBsb3QocDEscDIscDMsY29scyA9IDIpCmBgYApJdCBjbGVhcmx5IGxvb2tzIGxpa2UgdGhhdCB0d28gY2VsbHMgYWJvdmUgdG90YWwgY291bnRzIG9mIDMwMDAwIGFyZSBvdXRsaWVycy4gVGhpcyBhZ2FpbiB3b3VsZCBiZSByZW1vdmVkIGFzIGl0IHdvdWxkIG90aGVyd2lzZSBtaWdodCBiZSBtdWx0aXBsZXRzCgpgYGB7cn0KY2RTY0ZpbHQKYGBgCmBgYHtyfQpjZFNjRmlsdCA8LSBjZFNjRmlsdFssIShjZFNjRmlsdCR0b3RhbF9jb3VudHMgPiAzMDAwMCldCmBgYAoKYGBge3J9CmNkU2NGaWx0CmBgYAoKRHJhd2luZyB0aGUgdnVpb2xpbiBwbG90IGFnYWluCgpgYGB7ciBmaWdWaW9saW5fdjIsIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoID0gOX0KZGYgPC0gZGF0YS5mcmFtZShDZWxsPWNvbG5hbWVzKGNkU2NGaWx0KSwgQ2VsbFR5cGU9Y2RTY0ZpbHQkU2FtcGxlLCB0b3RhbEZlYXR1cmVzPWNkU2NGaWx0JHRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cywgdG90YWxDb3VudD1jZFNjRmlsdCR0b3RhbF9jb3VudHMsIFBjdFRvdGFsQ291bnRNdD1jZFNjRmlsdCRwY3RfY291bnRzX010LCBTYW1wbGU9Y2RTY0ZpbHQkU2FtcGxlKQpwMSA8LSBnZ3Bsb3QoZGYsIGFlcyhmYWN0b3IoQ2VsbFR5cGUpLHRvdGFsQ291bnQsY29sb3VyPVNhbXBsZSkpCnAxIDwtIHAxICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCnAyIDwtIGdncGxvdChkZiwgYWVzKGZhY3RvcihDZWxsVHlwZSksUGN0VG90YWxDb3VudE10LGNvbG91cj1TYW1wbGUpKQpwMiA8LSBwMiArIGdlb21fdmlvbGluKCkgKyBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMSkgKyB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE0KSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSkpCgpwMyA8LSBnZ3Bsb3QoZGYsIGFlcyhmYWN0b3IoQ2VsbFR5cGUpLHRvdGFsRmVhdHVyZXMsY29sb3VyPVNhbXBsZSkpCnAzIDwtIHAzICsgZ2VvbV92aW9saW4oKSArIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4xKSArIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTQpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xKSkKCgptdWx0aXBsb3QocDEscDIscDMsY29scyA9IDIpCmBgYApMb29rcyBhIGdvb2QgZGlzdHJpYnV0aW9uIHRvIG1vdmUgZm9yd2FyZC4KCmBgYHtyfQpzYXZlLmltYWdlKCkKYGBgCgoKCiMgQ2xhc3NpZmljYXRpb24gb2YgY2VsbCBjeWNsZSBwaGFzZQoKV2UgdXNlIHRoZSBwcmVkaWN0aW9uIG1ldGhvZCBkZXNjcmliZWQgYnkgW1NjaWFsZG9uZSBldCBhbC4gKDIwMTUpXShodHRwOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzEwNDYyMDIzMTUzMDAwOTgpIHRvIGNsYXNzaWZ5IGNlbGxzIGludG8gY2VsbCBjeWNsZSBwaGFzZXMgYmFzZWQgb24gdGhlIGdlbmUgZXhwcmVzc2lvbiBkYXRhLiBVc2luZyBhIHRyYWluaW5nIGRhdGFzZXQsIHRoZSBzaWduIG9mIHRoZSBkaWZmZXJlbmNlIGluIGV4cHJlc3Npb24gYmV0d2VlbiB0d28gZ2VuZXMgd2FzIGNvbXB1dGVkIGZvciBlYWNoIHBhaXIgb2YgZ2VuZXMuIFBhaXJzIHdpdGggY2hhbmdlcyBpbiB0aGUgc2lnbiBhY3Jvc3MgY2VsbCBjeWNsZSBwaGFzZXMgd2VyZSBjaG9zZW4gYXMgbWFya2Vycy4gQ2VsbHMgaW4gYSB0ZXN0IGRhdGFzZXQgY2FuIHRoZW4gYmUgY2xhc3NpZmllZCBpbnRvIHRoZSBhcHByb3ByaWF0ZSBwaGFzZSwgYmFzZWQgb24gd2hldGhlciB0aGUgb2JzZXJ2ZWQgc2lnbiBmb3IgZWFjaCBtYXJrZXIgcGFpciBpcyBjb25zaXN0ZW50IHdpdGggb25lIHBoYXNlIG9yIGFub3RoZXIuIFdlIGRvIHRoZSBjZWxsIGN5Y2xlIGNsYXNzaWZpY2F0aW9uIGJlZm9yZSBnZW5lIGZpbHRlcmluZyBhcyB0aGlzIHByb3ZpZGVzIG1vcmUgcHJlY2lzZSBjZWxsIGN5Y2xlIHBoYXNlIGNsYXNzaWZpY2F0aW9ucy4gVGhpcyBhcHByb2FjaCBpcyBpbXBsZW1lbnRlZCBpbiB0aGUgQ3ljbG9uZSBmdW5jdGlvbiB1c2luZyBhIHByZS10cmFpbmVkIHNldCBvZiBtYXJrZXIgcGFpcnMgZm9yIGh1bWFuIGRhdGEuIFNvbWUgYWRkaXRpb25hbCB3b3JrIGlzIG5lY2Vzc2FyeSB0byBtYXRjaCB0aGUgZ2VuZSBzeW1ib2xzIGluIHRoZSBkYXRhIHRvIHRoZSBFbnNlbWJsIGFubm90YXRpb24gaW4gdGhlIHByZS10cmFpbmVkIG1hcmtlciBzZXQuCgpgYGB7cn0KY2RTY0ZpbHRBbm5vdCA8LSBjZFNjRmlsdApgYGAKCmBgYHtyfQpoZy5wYWlycyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleGRhdGEiLCAiaHVtYW5fY3ljbGVfbWFya2Vycy5yZHMiLCBwYWNrYWdlPSJzY3JhbiIpKQpsaWJyYXJ5KG9yZy5Icy5lZy5kYikKYW5ubyA8LSBzZWxlY3Qob3JnLkhzLmVnLmRiLCBrZXlzPWFzLmNoYXJhY3Rlcihyb3duYW1lcyhjZFNjRmlsdEFubm90KSksIGtleXR5cGU9IlNZTUJPTCIsIGNvbHVtbj0iRU5TRU1CTCIpCmVuc2VtYmwgPC0gYW5ubyRFTlNFTUJMW21hdGNoKGFzLmNoYXJhY3Rlcihyb3duYW1lcyhjZFNjRmlsdEFubm90KSksIGFubm8kU1lNQk9MKV0KYXNzaWdubWVudHMgPC0gY3ljbG9uZShjZFNjRmlsdEFubm90LCBoZy5wYWlycywgZ2VuZS5uYW1lcz1lbnNlbWJsKQpgYGAKCmBgYHtyfQpkZiA8LSBkYXRhLmZyYW1lKHg9YXNzaWdubWVudHMkc2NvcmUkRzEsIHk9YXNzaWdubWVudHMkc2NvcmUkRzJNLCBTYW1wbGU9Y29sRGF0YShjZFNjRmlsdCkkU2FtcGxlKQogcDwtZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PXgseT15LGNvbG9yPVNhbXBsZSkpICsgCiAgICBnZW9tX3BvaW50KHNpemU9MC41KSsKICAgIHhsYWIoIkcxIHNjb3JlIikrCiAgICB5bGFiKCJHMk0gc2NvcmUiKSsKICAgIHlsaW0oMCwxKSsKICAgIHhsaW0oMCwxKSsKICAgIGdndGl0bGUoIkNlbGwtY3ljbGUgZWZmZWN0cyIpKwogICAgdGhlbWVfbGlnaHQoYmFzZV9zaXplPTE1KSsKICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEwLCB2anVzdD0tMiksCiAgICAgICAgICBheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQoIHNpemU9MTApLAogICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KCBzaXplPTEwLHZqdXN0PTIpLAogICAgICAgICAgYXhpcy50ZXh0LnkgID0gZWxlbWVudF90ZXh0KCBzaXplPTEwKSkgKwogICAgdGhlbWUocGxvdC5tYXJnaW49dW5pdChjKDEsMSwxLjUsMS4yKSwiY20iKSkrCiAgICB0aGVtZShsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMCksI3NpemUgb2YgbGVnZW5kCiAgICAgICAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0yMCwgZmFjZT0iYm9sZCIpKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsgCiAgICAjc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZT1sZWdlbmQudGl0bGUpKwogICAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMS8yLCB5ID0gMCwgeGVuZD0xLzIsIHllbmQ9MS8yKSxjb2xvdXI9ImJsYWNrIikgKyAKICAgIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSAxLzIsIHhlbmQ9MS8yLCB5ZW5kPTEvMiksY29sb3VyPSJibGFjayIpICsKICAgIGdlb21fc2VnbWVudChhZXMoeCA9IDEvMiwgeSA9IDEvMiwgeGVuZD0xLCB5ZW5kPTEpLGNvbG91cj0iYmxhY2siKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHg9MC4wNSwgeT0wLjA1LCBsYWJlbD0iUyIsIHNpemU9OCkrCiAgICBhbm5vdGF0ZSgidGV4dCIsIHg9MC45NSwgeT0wLjI1LCBsYWJlbD0iRzEiLCBzaXplPTgpKwogICAgYW5ub3RhdGUoInRleHQiLCB4PTAuMjUsIHk9MC45NSwgbGFiZWw9IkcyTSIsIHNpemU9OCkKcHJpbnQocCkKc21vb3RoU2NhdHRlcihhc3NpZ25tZW50cyRzY29yZSRHMSwgYXNzaWdubWVudHMkc2NvcmUkRzJNLCB4bGFiPSJHMSBzY29yZSIsIHlsYWI9IkcyL00gc2NvcmUiLCBwY2g9MTYsIGNleD0wLjYpCmBgYAoKQ2VsbHMgYXJlIGNsYXNzaWZpZWQgYXMgYmVpbmcgaW4gRzEgcGhhc2UgaWYgdGhlIEcxIHNjb3JlIGlzIGFib3ZlIDAuNSBhbmQgZ3JlYXRlciB0aGFuIHRoZSBHMi9NIHNjb3JlOyBpbiBHMi9NIHBoYXNlIGlmIHRoZSBHMi9NIHNjb3JlIGlzIGFib3ZlIDAuNSBhbmQgZ3JlYXRlciB0aGFuIHRoZSBHMSBzY29yZTsgYW5kIGluIFMgcGhhc2UgaWYgbmVpdGhlciBzY29yZSBpcyBhYm92ZSAwLjUuIEhlcmUsIHRoZSB2YXN0IG1ham9yaXR5IG9mIGNlbGxzIGFyZSBjbGFzc2lmaWVkIGFzIGJlaW5nIGluIEcxIHBoYXNlLiAKCkl0IGxvb2tzIGxpa2UgbWFqb3JpdHkgb2YgdGhlIGNlbGxzIGFyZSBpbiBoaWdoIEcyTSBvciBTIHNjb3JlIGluZGljYXRpbmcgdGhhdCB0aGUgY2VsbHMgYXJlIF9fSU5ERUVEX18gZ29pbmcgdGhyb3VnaCBjZWxsLWN5Y2xlIHN0YWdlcy4gVGhpcyBpcyB3ZSB3b3VsZCBhbHNvIGV4cGVjdCBmcm9tIENhbmNlciBjZWxscy4gCgpUaGlzIG1ldGhvZCB3b3VsZCBiZSBsZXNzIGFjY3VyYXRlIGZvciBkYXRhIHRoYXQgYXJlIHN1YnN0YW50aWFsbHkgZGlmZmVyZW50IGZyb20gdGhvc2UgdXNlZCBpbiB0aGUgdHJhaW5pbmcgc2V0LCBlLmcuLCBkdWUgdG8gdGhlIHVzZSBvZiBhIGRpZmZlcmVudCBwcm90b2NvbC4gVGhpcyBkYXRhc2V0IHVzZXMgVU1JIGNvdW50cywgd2hpY2ggaGFzIGFuIGVudGlyZWx5IGRpZmZlcmVudCBzZXQgb2YgYmlhc2VzLCBlLmcuLCAz4oCZLWVuZCBjb3ZlcmFnZSBvbmx5LCBubyBsZW5ndGggYmlhcywgbm8gYW1wbGlmaWNhdGlvbiBub2lzZS4gVGhlc2UgbmV3IGJpYXNlcyAoYW5kIHRoZSBhYnNlbmNlIG9mIGV4cGVjdGVkIGJpYXNlcykgbWF5IGludGVyZmVyZSB3aXRoIGFjY3VyYXRlIGNsYXNzaWZpY2F0aW9uIG9mIHNvbWUgY2VsbHMuIFNvIHdlIGFyZSBub3QgcGFydGljdWxhcmx5IHN1cmUgYWJvdXQgdGhpcyByZXN1bHQuIE5ldmVydGhlbGVzcyB3ZSBuZWVkIHRvIGtlZXAgaW4gbWluZCB0aGF0IHRoZXJlIGNvdWxkIGJlIHF1aXRlIGhpZ2ggY2VsbC1jeWNsZSBlZmZlY3Qgd2hpY2ggbWlnaHQgY29uZm91bmQgdGhlIGRhdGFzZXQuIFRvIGF2b2lkIHByb2JsZW1zIGZyb20gbWlzY2xhc3NpZmljYXRpb24sIEkgd2lsbCBub3QgcGVyZm9ybSBhbnkgcHJvY2Vzc2luZyBvZiB0aGlzIGRhdGFzZXQgYnkgY2VsbCBjeWNsZSBwaGFzZS4gVGhpcyBpcyB1bmxpa2VseSB0byBiZSBwcm9ibGVtYXRpYyBmb3IgdGhpcyBhbmFseXNpcywgYXMgdGhlIGNlbGwgY3ljbGUgZWZmZWN0IHdpbGwgYmUgcmVsYXRpdmVseSBzdWJ0bGUgY29tcGFyZWQgdG8gdGhlIG9idmlvdXMgZGlmZmVyZW5jZXMgYmV0d2VlbiBjZWxsIHR5cGVzIGluIGEgZGl2ZXJzZSBwb3B1bGF0aW9uLiBUaHVzLCB0aGUgZm9ybWVyIGlzIHVubGlrZWx5IHRvIGRpc3RvcnQgdGhlIGNvbmNsdXNpb25zIHJlZ2FyZGluZyB0aGUgbGF0dGVyLgoKYGBge3J9CmNvbERhdGEoY2RTY0ZpbHQpJENlbGxDeWNsZSA8LSBhc3NpZ25tZW50cyRwaGFzZXMKY29sRGF0YShjZFNjRmlsdEFubm90KSRDZWxsQ3ljbGUgPC0gYXNzaWdubWVudHMkcGhhc2VzCnBsb3RQQ0EoY2RTY0ZpbHRBbm5vdCwgY29sb3VyX2J5ID0gIkNlbGxDeWNsZSIsIHNoYXBlX2J5PSJTYW1wbGUiKQpgYGAKCkl0IGRvZXMgbm90IGxvb2sgbGlrZSBvbmUgcGF0aWVudCBpcyBkb21pbmF0ZWQgYnkgYSBjZWxsLWN5Y2xlIHBoYXNlLiAKCgojIEZpbHRlcmluZyBvdXQgbG93LWFidW5kYW5jZSBnZW5lcwpMb3ctYWJ1bmRhbmNlIGdlbmVzIGFyZSBwcm9ibGVtYXRpYyBhcyB6ZXJvIG9yIG5lYXItemVybyBjb3VudHMgZG8gbm90IGNvbnRhaW4gZW5vdWdoIGluZm9ybWF0aW9uIGZvciByZWxpYWJsZSBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgKFtCb3VyZ29uIGV0IGFsLiwgMjAxMF0oaHR0cDovL3d3dy5wbmFzLm9yZy9jb250ZW50LzEwNy8yMS85NTQ2KSkuIEluIGFkZGl0aW9uLCB0aGUgZGlzY3JldGVuZXNzIG9mIHRoZSBjb3VudHMgbWF5IGludGVyZmVyZSB3aXRoIGRvd25zdHJlYW0gc3RhdGlzdGljYWwgcHJvY2VkdXJlcywgZS5nLiwgYnkgY29tcHJvbWlzaW5nIHRoZSBhY2N1cmFjeSBvZiBjb250aW51b3VzIGFwcHJveGltYXRpb25zLiAKCkdlbmVyYWxseSBmb3IgbG93LWFidW5kYW5jZSBnZW5lcyBhcmUgZGVmaW5lZCBhcyB0aG9zZSB3aXRoIGFuIGF2ZXJhZ2UgY291bnQgYmVsb3cgYSBmaWx0ZXIgdGhyZXNob2xkIG9mIDEuIEJ1dCAxMFggQ2hyb21pdW0gaXMgYmFzZWQgb24gVU1JIGNvdW50cywgd2hpY2ggd291bGQgaGF2ZSB1bmRlcnN0YW5kYWJseSBsb3cgY291bnRzLCBzbyBzZXR0aW5nIHRoZSB0aHJlc2hvbGQgdG8gMSB3b3VsZCBmaWx0ZXIgYSBsYXJnZSBudW1iZXIgb2YgY2VsbHMuIEhlcmUgSSBhbSBnb2luZyB0byBzZXQgaXQgdG8gMC4wMS4gVGhlIGZpZ3VyZSBiZWxvdyB3aWxsIGV4cGxhaW4gdGhlIHJlYXNvbi4KClRoZXNlIGdlbmVzIGFyZSBsaWtlbHkgdG8gYmUgZG9taW5hdGVkIGJ5IGRyb3Atb3V0IGV2ZW50cyAoW0JyZW5uZWNrZSBldCBhbC4sIDIwMTNdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm1ldGguMjY0NSkpLCB3aGljaCBsaW1pdHMgdGhlaXIgdXNlZnVsbmVzcyBpbiBsYXRlciBhbmFseXNlcy4gUmVtb3ZhbCBvZiB0aGVzZSBnZW5lcyBtaXRpZ2F0ZXMgZGlzY3JldGVuZXNzIGFuZCByZWR1Y2VzIHRoZSBhbW91bnQgb2YgY29tcHV0YXRpb25hbCB3b3JrIHdpdGhvdXQgbWFqb3IgbG9zcyBvZiBpbmZvcm1hdGlvbi4KCmBgYHtyfQphdmUuY291bnRzIDwtIHJvd01lYW5zKGNvdW50cyhjZFNjRmlsdCkpCmtlZXAgPC0gYXZlLmNvdW50cyA+PSAwLjAxCnN1bShrZWVwKQpgYGAKQWZ0ZXIgZmlsdGVyaW5nIHdpdGggYXZlcmFnZSBjb3VudCBvZiAwLjAxIHRoZXJlIGFyZSAxMzIxMCBnZW5lcyBsZWZ0IGZvciB0aGUgZG93bnN0ZXJhbSBhbmFseXNpcy4gCgpUbyBjaGVjayB3aGV0aGVyIHRoZSBjaG9zZW4gdGhyZXNob2xkIGlzIHN1aXRhYmxlLCB3ZSBleGFtaW5lIHRoZSBkaXN0cmlidXRpb24gb2YgbG9nLW1lYW5zIGFjcm9zcyBhbGwgZ2VuZXMgKEZpZ3VyZSBiZWxvdykuIEdlbmVyYWxseSBmb3IgaGlnaGVyIG51bWJlciBvZiBjZWxscyB0aGVyZSBpcyBhIHBlYWsgb24gdGhlIHJpZ2h0IGhhbmQgc2lkZSB0aGF0IHJlcHJlc2VudHMgdGhlIGJ1bGsgb2YgbW9kZXJhdGVseSBleHByZXNzZWQgZ2VuZXMgd2hpbGUgaW4gdGhlIG1pZGRsZSB0aGVyZSBpcyBhIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCB0aGF0IGNvcnJlc3BvbmRzIHRvIGxvd2x5IGV4cHJlc3NlZCBnZW5lcy4gVGhlIGZpbHRlciB0aHJlc2hvbGQgc2hvdWxkIGN1dCB0aGUgZGlzdHJpYnV0aW9uIGF0IHNvbWUgcG9pbnQgYWxvbmcgdGhlIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCB0byByZW1vdmUgdGhlIG1ham9yaXR5IG9mIGxvdy1hYnVuZGFuY2UgZ2VuZXMuIEFzIHRoZSBibHVlIGxpbmUgcmVwc3Jlc2VudHMgaW4gdGhlIGZpZ3VyZSBiZWxvdywgaXQgY3V0cyB0aGUgY291bnRzIGF0IHRoZSByZWN0YW5ndWxhciBjb21wb25lbnQuCgpgYGB7cn0KaGlzdChsb2cxMChhdmUuY291bnRzKSwgYnJlYWtzPTEwMCwgbWFpbj0iIiwgY29sPSJncmV5ODAiLAogICAgIHhsYWI9ZXhwcmVzc2lvbihMb2dbMTBdfiJhdmVyYWdlIGNvdW50IikpCmFibGluZSh2PWxvZzEwKDAuMDEpLCBjb2w9ImJsdWUiLCBsd2Q9MiwgbHR5PTIpCmBgYAoKV2Ugd2lsbCBsb29rIGF0IHRoZSBpZGVudGl0aWVzIG9mIHRoZSBtb3N0IGhpZ2hseSBleHByZXNzZWQgZ2VuZXMgYmVmb3JlIGZpbHRlcmluZyB0aGVtLiBUaGlzIHNob3VsZCBnZW5lcmFsbHkgYmUgZG9taW5hdGVkIGJ5IGNvbnN0aXR1dGl2ZWx5IGV4cHJlc3NlZCB0cmFuc2NyaXB0cywgc3VjaCBhcyB0aG9zZSBmb3Igcmlib3NvbWFsIG9yIG1pdG9jaG9uZHJpYWwgcHJvdGVpbnMuIFRoZSBwcmVzZW5jZSBvZiBvdGhlciBjbGFzc2VzIG9mIGZlYXR1cmVzIG1heSBiZSBjYXVzZSBmb3IgY29uY2VybiBpZiB0aGV5IGFyZSBub3QgY29uc2lzdGVudCB3aXRoIGV4cGVjdGVkIGJpb2xvZ3kuIEZvciBleGFtcGxlLCB0aGUgYWJzZW5jZSBvZiByaWJvc29tYWwgcHJvdGVpbnMgYW5kL29yIHRoZSBwcmVzZW5jZSBvZiB0aGVpciBwc2V1ZG9nZW5lcyBhcmUgaW5kaWNhdGl2ZSBvZiBzdWJvcHRpbWFsIGFsaWdubWVudC4KCmBgYHtyfQpwbG90SGlnaGVzdEV4cHJzKGNkU2NGaWx0QW5ub3QpCmBgYAoKYGBge3J9CnBkZignSGlnaGVzdF9leHByZXNzaW9uXzUwR2VuZS5wZGYnKQpwbG90SGlnaGVzdEV4cHJzKGNkU2NGaWx0QW5ub3QpCmRldi5vZmYoKQpgYGAKClBlcmNlbnRhZ2Ugb2YgdG90YWwgY291bnRzIGFzc2lnbmVkIHRvIHRoZSB0b3AgNTAgbW9zdCBoaWdobHktYWJ1bmRhbnQgZmVhdHVyZXMgaW4gdGhlIGRhdGFzZXQuCgpGb3IgZWFjaCBmZWF0dXJlLCBlYWNoIGJhciByZXByZXNlbnRzIHRoZSBwZXJjZW50YWdlIGFzc2lnbmVkIHRvIHRoYXQgZmVhdHVyZSBmb3IgYSBzaW5nbGUgY2VsbCwgd2hpbGUgdGhlIGNpcmNsZSByZXByZXNlbnRzIHRoZSBhdmVyYWdlIGFjcm9zcyBhbGwgY2VsbHMuIEJhcnMgYXJlIGNvbG91cmVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgZXhwcmVzc2VkIGZlYXR1cmVzIGluIGVhY2ggY2VsbCwgd2hpbGUgY2lyY2xlcyBhcmUgY29sb3VyZWQgYWNjb3JkaW5nIHRvIHdoZXRoZXIgdGhlIGZlYXR1cmUgaXMgbGFiZWxsZWQgYXMgYSBjb250cm9sIGZlYXR1cmUuCgpfX0RvIHRoZSB0b3Btb3N0IGdlbmVzIGFwcGVhciB0byBhZ3JlZSB3aXRoIHRoZSBiaW9sb2d5P19fCgpfX0xvdWlzYTogUGxlYXNlIGxvb2sgYXQgdGhlIGdlbmVzIHdoZXRoZXIgdGhvc2UgbWFrZSBzZW5zZS4uLi5fX18KCk5vdyBmb3IgdGhlIGZpbHRlaXJuZywgSSBnZW5lcmFsbHkgcHJlZmVyIHRoZSBtZWFuLWJhc2VkIGZpbHRlciBhcyBpdCB0ZW5kcyB0byBiZSBsZXNzIGFnZ3Jlc3NpdmUuIEEgZ2VuZSB3aWxsIGJlIHJldGFpbmVkIGFzIGxvbmcgYXMgaXQgaGFzIHN1ZmZpY2llbnQgZXhwcmVzc2lvbiBpbiBhbnkgc3Vic2V0IG9mIGNlbGxzLiBHZW5lcyBleHByZXNzZWQgaW4gZmV3ZXIgY2VsbHMgcmVxdWlyZSBoaWdoZXIgbGV2ZWxzIG9mIGV4cHJlc3Npb24gaW4gdGhvc2UgY2VsbHMgdG8gYmUgcmV0YWluZWQsIGJ1dCB0aGlzIGlzIG5vdCB1bmRlc2lyYWJsZSBhcyBpdCBhdm9pZHMgc2VsZWN0aW5nIHVuaW5mb3JtYXRpdmUgZ2VuZXMgKHdpdGggbG93IGV4cHJlc3Npb24gaW4gZmV3IGNlbGxzKSB0aGF0IGNvbnRyaWJ1dGUgbGl0dGxlIHRvIGRvd25zdHJlYW0gYW5hbHlzZXMsIGUuZy4sIEhWRyBkZXRlY3Rpb24gb3IgY2x1c3RlcmluZy4gSW4gY29udHJhc3QsIHRoZSDigJxhdCBsZWFzdCBu4oCdIGZpbHRlciBkZXBlbmRzIGhlYXZpbHkgb24gdGhlIGNob2ljZSBvZiBuLiBXaXRoIG4gPSAxMCwgYSBnZW5lIGV4cHJlc3NlZCBpbiBhIHN1YnNldCBvZiA5IGNlbGxzIHdvdWxkIGJlIGZpbHRlcmVkIG91dCwgcmVnYXJkbGVzcyBvZiB0aGUgbGV2ZWwgb2YgZXhwcmVzc2lvbiBpbiB0aG9zZSBjZWxscy4gVGhpcyBtYXkgcmVzdWx0IGluIHRoZSBmYWlsdXJlIHRvIGRldGVjdCByYXJlIHN1YnBvcHVsYXRpb25zIHRoYXQgYXJlIHByZXNlbnQgYXQgZnJlcXVlbmNpZXMgYmVsb3cgbi4gV2hpbGUgdGhlIG1lYW4tYmFzZWQgZmlsdGVyIHdpbGwgcmV0YWluIG1vcmUgb3V0bGllci1kcml2ZW4gZ2VuZXMsIHRoaXMgY2FuIGJlIGhhbmRsZWQgYnkgY2hvb3NpbmcgbWV0aG9kcyB0aGF0IGFyZSByb2J1c3QgdG8gb3V0bGllcnMgaW4gdGhlIGRvd25zdHJlYW0gYW5hbHlzZXMuCgpUaHVzLCB3ZSBhcHBseSB0aGUgbWVhbi1iYXNlZCBmaWx0ZXIgdG8gdGhlIGRhdGEgYnkgc3Vic2V0dGluZyB0aGUgU0NFU2V0IG9iamVjdCBhcyBzaG93biBiZWxvdy4gVGhpcyByZW1vdmVzIGFsbCByb3dzIGNvcnJlc3BvbmRpbmcgdG8gZW5kb2dlbm91cyBnZW5lcyBvciBzcGlrZS1pbiB0cmFuc2NyaXB0cyB3aXRoIGFidW5kYW5jZXMgYmVsb3cgdGhlIHNwZWNpZmllZCB0aHJlc2hvbGQuCgpgYGB7cn0KY2RTY0ZpbHQgPC0gY2RTY0ZpbHRba2VlcCxdCmNkU2NGaWx0QW5ub3QgPC0gY2RTY0ZpbHQKI2NkU2NGaWx0QW5ub3QgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKGNkU2NGaWx0QW5ub3QpCmBgYApgYGB7cn0KY2RTY0ZpbHRBbm5vdApgYGAKClRoZSBmaWx0ZXJpbmcgbm93IGxldHMgc2VlIGhvdyB0aGUgdG90YWwgY291bnRzIGFuZCBmZWF0dXJlcyBwbG90czoKYGBge3J9CnAgPC0gcGxvdENvbERhdGEoY2RTY0ZpbHQsIHg9ImxvZzEwX3RvdGFsX2NvdW50cyIseT0idG90YWxfZmVhdHVyZXNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gIlNhbXBsZSIpICArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpIApwCmBgYAoKV2hhdCBpcyB0aGUgZnJlcXVlbmN5IG9mIHRoZSBjZWxscyB3aXRoIG1lYW4gZXhwcmVzc2lvbj86CmBgYHtyfQpwbG90RXhwcnNGcmVxVnNNZWFuKGNkU2NGaWx0QW5ub3QpCmBgYApPdmVyYWxsIGxvb2tzIGEgcmVhc29uYWJsZSBudW1iZXIgb2YgZ2VuZXMgZXhwcmVzc2VkIGluIDUwJSBvZiBjZWxscy4gCgpgYGB7ciBmaWdRQ0ZlYXR1cmVzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD05fQpwIDwtIHBsb3RDb2xEYXRhKGNkU2NGaWx0LCB4PSJ0b3RhbF9jb3VudHMiLHk9InRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsIGNvbG91cl9ieSA9ICJTYW1wbGUiKSArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgCgpwMiA8LSBwbG90Um93RGF0YShjZFNjRmlsdCwgeCA9ICJsb2cxMF90b3RhbF9jb3VudHMiLCB5ID0gIm5fY2VsbHNfYnlfY291bnRzIiwgY29sb3VyX2J5ID0gInBjdF9kcm9wb3V0X2J5X2NvdW50cyIpCgptdWx0aXBsb3QocGxvdFBDQShjZFNjRmlsdCwgY29sb3VyX2J5PSJTYW1wbGUiLAogICAgIHNpemVfYnk9InRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgLAogICAgICAgICAgcCwKICAgICAgICAgIHAyLAogICAgICAgICAgY29scz0yKQpgYGAKCgpgYGB7cn0Kc3VtbWFyeShyb3dEYXRhKGNkU2NGaWx0KSRwY3RfZHJvcG91dF9ieV9jb3VudHMpCmBgYAoKU29tZSBvZiB0aGUgZ2VuZXMgaW4gdGhlIHRvcCByaWdodCBmaWd1cmUgc3RpbGwgaGFzIGhpZ2ggZHJvcC1vdXRzLiBUaGlzIGlzIG5vdCB1bnVzdWFsIGZvciBhIHNpbmdsZS1jZWxsIFJOQS1zZXEgYXMgdGhlIGRhdGEgaXMgdmVyeSBzcGFyc2UgYW5kIHNwZWNpYWxseSB3aGVuIHVzaW5nIFVNSSBiYXNlZCBjb3VudHMuIElmIHRoZXJlIGFyZSBnZW5lcyB0aGF0IGhhdmUgdmVyeSBoaWdoIHJlYWQgY291bnRzLCBidXQgYXJlIHRoZW4gZXhwcmVzc2VkIG9ubHkgdmVyeSBmZXcgY2VsbHMsIHRoYXQgd291bGQgYmUgd29ycnlpbmcgYXMgaXQgd291bGQgaW5kaWNhdGUgdGhhdCB0aG9zZSBjZWxscyBhcmUgYmVoYXZpbmcgYWJydXB0bHkuCgpfX1NvIHRoaW5ncyBsb29rIGZpbmUgaW4gUUMuX18KCgojIyBOb3JtYWxpemF0aW9uIG9mIGNlbGwtc3BlY2lmaWMgYmlhc2VzCk5vdywgd2Ugd291bGQgYmUgZG9pbmcgc3RlcCBzaXplIGJhc2VkIG5vcm1hbGl6YXRpb24uIE9uZSBvZiB0aGUgd2lkZWx5IHVzZWQgc3RlcC13aXNlIG5vcm1hbGl6YXRpb24gdGVjaG5pcXVlIGZvciBCdWxrIGRhdGEgaXMgdGhlIG9uZSB1c2VkIGJ5IERFU2VxMi4gSG93ZXZlciwgYXMgdGhlIHNpbmdsZSBjZWxsIGRhdGEgaXMgdmVyeSBzcGFyc2Ugd2UgY2Fubm90IHVzZSB0aGlzIG1ldGhvZC4gVGhlIG90aGVyIHZlcnkgZWZmaWNpZW50IG1ldGhvZCBpcyB0aGUgZGVjb252b2x1dGlvbiBiYXNlZCBtZXRob2Qgd2hpY2ggd2Ugd291bGQgYmUgdXNpbmcgaGVyZS4KCl9fVXNpbmcgdGhlIGRlY29udm9sdXRpb24gbWV0aG9kIHRvIGRlYWwgd2l0aCB6ZXJvIGNvdW50czpfXyAKUmVhZCBjb3VudHMgYXJlIHN1YmplY3QgdG8gZGlmZmVyZW5jZXMgaW4gY2FwdHVyZSBlZmZpY2llbmN5IGFuZCBzZXF1ZW5jaW5nIGRlcHRoIGJldHdlZW4gY2VsbHMgKFtTdGVnbGUgZXQgYWwuLCAyMDE1XShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL25yZzM4MzMpKS4gTm9ybWFsaXphdGlvbiBpcyByZXF1aXJlZCB0byBlbGltaW5hdGUgdGhlc2UgY2VsbC1zcGVjaWZpYyBiaWFzZXMgcHJpb3IgdG8gZG93bnN0cmVhbSBxdWFudGl0YXRpdmUgYW5hbHlzZXMuIFRoaXMgaXMgb2Z0ZW4gZG9uZSBieSBhc3N1bWluZyB0aGF0IG1vc3QgZ2VuZXMgYXJlIG5vdCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgKERFKSBiZXR3ZWVuIGNlbGxzLiBBbnkgc3lzdGVtYXRpYyBkaWZmZXJlbmNlIGluIGNvdW50IHNpemUgYWNyb3NzIHRoZSBub24tREUgbWFqb3JpdHkgb2YgZ2VuZXMgYmV0d2VlbiB0d28gY2VsbHMgaXMgYXNzdW1lZCB0byByZXByZXNlbnQgYmlhcyBhbmQgaXMgcmVtb3ZlZCBieSBzY2FsaW5nLiBNb3JlIHNwZWNpZmljYWxseSwg4oCcc2l6ZSBmYWN0b3Jz4oCdIGFyZSBjYWxjdWxhdGVkIHRoYXQgcmVwcmVzZW50IHRoZSBleHRlbnQgdG8gd2hpY2ggY291bnRzIHNob3VsZCBiZSBzY2FsZWQgaW4gZWFjaCBsaWJyYXJ5LgoKU2luZ2xlLWNlbGwgZGF0YSBjYW4gYmUgcHJvYmxlbWF0aWMgZHVlIHRvIHRoZSBkb21pbmFuY2Ugb2YgbG93IGFuZCB6ZXJvIGNvdW50cy4gVG8gb3ZlcmNvbWUgdGhpcywgd2UgcG9vbCBjb3VudHMgZnJvbSBtYW55IGNlbGxzIHRvIGluY3JlYXNlIHRoZSBjb3VudCBzaXplIGZvciBhY2N1cmF0ZSBzaXplIGZhY3RvciBlc3RpbWF0aW9uIChbTHVuIGV0IGFsLiwgMjAxNl0oaHR0cHM6Ly9nZW5vbWViaW9sb2d5LmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczEzMDU5LTAxNi0wOTQ3LTcpKS4gUG9vbC1iYXNlZCBzaXplIGZhY3RvcnMgYXJlIHRoZW4g4oCcZGVjb252b2x2ZWTigJ0gaW50byBjZWxsLWJhc2VkIGZhY3RvcnMgZm9yIGNlbGwtc3BlY2lmaWMgbm9ybWFsaXphdGlvbi4KYGBge3J9CmNkU2NGaWx0QW5ub3QgPC0gY29tcHV0ZVN1bUZhY3RvcnMoY2RTY0ZpbHRBbm5vdCwgc2l6ZXM9YygxMDAsIDIwMCwgMzAwLCA0MDAsIDQ0MCwgNDgwLCA1MDAsIDU1MCkpCnN1bW1hcnkoc2l6ZUZhY3RvcnMoY2RTY0ZpbHRBbm5vdCkpCmBgYAoKSWYgdGhlIHNpemUgZmFjdG9ycyBhcmUgdGlnaHRseSBjb3JyZWxhdGVkIHdpdGggdGhlIGxpYnJhcnkgc2l6ZXMgZm9yIGFsbCBjZWxscyB0aGlzIHdvdWxkIHN1Z2dlc3RzIHRoYXQgdGhlIHN5c3RlbWF0aWMgZGlmZmVyZW5jZXMgYmV0d2VlbiBjZWxscyBhcmUgcHJpbWFyaWx5IGRyaXZlbiBieSBkaWZmZXJlbmNlcyBpbiBjYXB0dXJlIGVmZmljaWVuY3kgb3Igc2VxdWVuY2luZyBkZXB0aC4gQW55IERFIGJldHdlZW4gY2VsbHMgd291bGQgeWllbGQgYSBub24tbGluZWFyIHRyZW5kIGJldHdlZW4gdGhlIHRvdGFsIGNvdW50IGFuZCBzaXplIGZhY3RvciwgYW5kL29yIGluY3JlYXNlZCBzY2F0dGVyIGFyb3VuZCB0aGUgdHJlbmQuCgpgYGB7cn0KREYgPC0gZGF0YS5mcmFtZShWQVIxPXNpemVGYWN0b3JzKGNkU2NGaWx0QW5ub3QpLCBWQVIyPWNkU2NGaWx0QW5ub3QkdG90YWxfY291bnRzLzFlNikKbW9kZWwgPSBsbShWQVIyIH4gVkFSMSwgREYpCnN1bW1hcnkobW9kZWwpCmBgYApgYGB7cn0Kc3AgPSBnZ3Bsb3QoREYsIGFlcyh4PVZBUjEsIHk9VkFSMikpCnNwICsgZ2VvbV9wb2ludCgpICsgc3RhdF9zbW9vdGgobWV0aG9kPWxtKSArIHRoZW1lX2xpZ2h0KGJhc2Vfc2l6ZT0xNSkgKwogICAgICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKyAgCiAgICAgICAgICAgICAgeGxhYigiU2l6ZSBGYWN0b3IiKSArIAogICAgICAgICAgICAgIHlsYWIoIkxpYnJhcnkgU2l6ZSAobWlsbGlvbnMpIikrCiAgICAgICAgICAgICAgYW5ub3RhdGUoInRleHQiLCBsYWJlbD0iUl4yPTAuODc2MiIsIHg9Mi44LCB5PTAuMDc1KQpgYGAKV2l0aCBhIGhpZ2ggUl4yIHZhbHVlLCB0aGUgc2l6ZSBmYWN0b3JzIGFyZSB2ZXJ5IHRpZ2h0bHkgY29ycmVsYXRlZCB3aXRoIHRoZSBsaWJyYXJ5IHNpemUuIEkgY2FsY3VsYXRlIHRoZSBzaXplIGZhY3RvciBub3JtaXphdGlvbiBhbmQgcHV0IGl0IGluIGEgc2xvdCBvZiBgU3VtbWFyaXplZEV4cGVyaW1lbnRgIG9iamVjdC4KCgpfX0FwcGx5aW5nIHRoZSBzaXplIGZhY3RvcnMgdG8gbm9ybWFsaXplIGdlbmUgZXhwcmVzc2lvbjpfXwoKVGhlIGNvdW50IGRhdGEgYXJlIHVzZWQgdG8gY29tcHV0ZSBub3JtYWxpemVkIGxvZy1leHByZXNzaW9uIHZhbHVlcyBmb3IgdXNlIGluIGRvd25zdHJlYW0gYW5hbHlzZXMuIEVhY2ggdmFsdWUgaXMgZGVmaW5lZCBhcyB0aGUgbG9nLXJhdGlvIG9mIGVhY2ggY291bnQgdG8gdGhlIHNpemUgZmFjdG9yIGZvciB0aGUgY29ycmVzcG9uZGluZyBjZWxsLCBhZnRlciBhZGRpbmcgYSBwcmlvciBjb3VudCBvZiAxIHRvIGF2b2lkIHVuZGVmaW5lZCB2YWx1ZXMgYXQgemVybyBjb3VudHMuIERpdmlzaW9uIGJ5IHRoZSBzaXplIGZhY3RvciBlbnN1cmVzIHRoYXQgYW55IGNlbGwtc3BlY2lmaWMgYmlhc2VzIGFyZSByZW1vdmVkLiBJZiBzcGlrZS1pbi1zcGVjaWZpYyBzaXplIGZhY3RvcnMgYXJlIHByZXNlbnQgaW4gc2NlLCB0aGV5IHdpbGwgYmUgYXV0b21hdGljYWxseSBhcHBsaWVkIHRvIG5vcm1hbGl6ZSB0aGUgc3Bpa2UtaW4gdHJhbnNjcmlwdHMgc2VwYXJhdGVseSBmcm9tIHRoZSBlbmRvZ2Vub3VzIGdlbmVzLgpgYGB7cn0KI2NkU2NGaWx0QW5ub3Q8LSBub3JtYWxpemUoY2RTY0ZpbHRBbm5vdCkKbm9ybV9leHBycyhjZFNjRmlsdEFubm90KTwtIHNjYXRlcjo6bm9ybWFsaXplKGNkU2NGaWx0QW5ub3QpCmBgYAoKIyMgUmUtY2FsY3VsYXRlIHRoZSBDUE0gbm9ybWFsaXphdGlvbgoKSSByZWNhbGN1bGF0ZSB0aGUgYGNvdW50cy1wZXItbWlsbGlvbmAgZm9yIHRoZXNlIGNlbGxzLiBUaGlzIGlzIGJlY2F1c2UgbGFyZ2UgbnVtYmVyIG9mIGdlbmVzIGhhdmUgYmVlbiBmaWx0ZXJlZCBvdXQgd2hpY2ggd291bGQgaGF2ZSBpbXBhY3RlZCB0aGUgbGlicmFyeSBzaXplIHdoZW4gQ1BNIHdhcyBjYWxjdWFsdGVkIHdpdGggdW5maWx0ZXJlZCBkYXRhLiBOb3csIGFzIHRoZSBudW1iZXIgb2YgZ2VuZXMgaGF2ZSBiZWVuIHJlZHVjZWQsIHNvIGlzIHRoZSBsaWJyYXJ5IHNpemUuIApgYGB7cn0KIyBjcG0oY2RTY0ZpbHRBbm5vdCkgPC0gbG9nMihjYWxjdWxhdGVDUE0oY2RTY0ZpbHRBbm5vdCwgdXNlX3NpemVfZmFjdG9ycyA9IFRSVUUpICsgMSkKZXhwcnMoY2RTY0ZpbHRBbm5vdCkgPC0gbG9nMihjYWxjdWxhdGVDUE0oY2RTY0ZpbHRBbm5vdCwgdXNlX3NpemVfZmFjdG9ycyA9IFRSVUUpICsgMSkKYGBgCgpJIHNldCB0aGUgcGFyYW1ldGVyIGB1c2Uuc2l6ZS5mYWN0b3JzID0gVFJVRWAuIFRoaXMgd291bGQgY29uc3RydWN0IHRoZSBlZmZlY3RpdmUgbGlicmFyeSBzaXplIGluc3RlYWQgb2YgdGhlIHN1bSBvZiBjb3VudHMgYXMgdGhlIGxpYnJhcnkgc2l6ZS4gVGhpcyBlZmZlY3RpdmUgbGlicmFyeSBzaXplIGlzIGRlZmluZWQgYmFzZWQgb24gdGhlIHN0ZXAgc2l6ZSBjYWxjdWFsdGVkIGFzIGRlc2NyaWJlZCBpbiB0aGUgcG9vbCBiYXNlZCBtZXRob2QgcGFwZXIuCgpUaGUgbG9nLXRyYW5zZm9ybWF0aW9uIHByb3ZpZGVzIHNvbWUgbWVhc3VyZSBvZiB2YXJpYW5jZSBzdGFiaWxpemF0aW9uIChbTGF3IGV0IGFsLiwgMjAxNF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDA1MzcyMS8pKSwgc28gdGhhdCBoaWdoLWFidW5kYW5jZSBnZW5lcyB3aXRoIGxhcmdlIHZhcmlhbmNlcyBkbyBub3QgZG9taW5hdGUgZG93bnN0cmVhbSBhbmFseXNlcy4gVGhlIGNvbXB1dGVkIHZhbHVlcyBhcmUgc3RvcmVkIGFzIGFuIGV4cHJzIG1hdHJpeCBpbiBhZGRpdGlvbiB0byB0aGUgb3RoZXIgYXNzYXkgZWxlbWVudHMuCgoKIyMgQ2hlY2tpbmcgZm9yIGltcG9ydGFudCB0ZWNobmljYWwgZmFjdG9ycwoKV2UgY2hlY2sgd2hldGhlciB0aGVyZSBhcmUgdGVjaG5pY2FsIGZhY3RvcnMgdGhhdCBjb250cmlidXRlIHN1YnN0YW50aWFsbHkgdG8gdGhlIGhldGVyb2dlbmVpdHkgb2YgZ2VuZSBleHByZXNzaW9uLiBJZiBzbywgdGhlIGZhY3RvciBtYXkgbmVlZCB0byBiZSByZWdyZXNzZWQgb3V0IHRvIGVuc3VyZSB0aGF0IGl0IGRvZXMgbm90IGluZmxhdGUgdGhlIHZhcmlhbmNlcyBvciBpbnRyb2R1Y2Ugc3B1cmlvdXMgY29ycmVsYXRpb25zLiBGb3IgdGhpcyBkYXRhc2V0LCB0aGUgc2ltcGxlIGV4cGVyaW1lbnRhbCBkZXNpZ24gbWVhbnMgdGhhdCB0aGVyZSBhcmUgbm8gcGxhdGUgb3IgYmF0Y2ggZWZmZWN0cyB0byBleGFtaW5lLiBIb3dldmVyIHRoZXJlIGNvdWxkIGJlIG90aGVyIHRlY2huaWNhbCBmYWN0b3JzIGxpa2UgdGhlIGNlbGwtY3ljbGUgZWZmZWN0IG9yIGRyb3BvdXRzCmBgYHtyfQpwbG90RXhwbGFuYXRvcnlWYXJpYWJsZXMoY2RTY0ZpbHRBbm5vdCwgdmFyaWFibGVzPWMoInRvdGFsX2ZlYXR1cmVzX2J5X2NvdW50cyIsICJ0b3RhbF9jb3VudHMiLCAiQ2VsbEN5Y2xlIiwgInBjdF9jb3VudHNfTXQiLCAiU2FtcGxlIikpCmBgYApBbW9uZyB0aGUgaW1wb3J0YW50IHRlY2huaWNhbCBmYWN0b3JzLCB3ZSBjYW4gc2VlIHRoYXQgbnVtYmVyIG9mIHRvdGFsIGZlYXR1cmVzIChnZW5lcykgYW5kIHRvdGFsIGNvdW50cyBhcmUgZXhwbGFpbmluZyB0aGUgbWFqb3IgdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGFzZXQuIFRoaXMgY29uZmlybXMgdGhhdCBjZWxscyB3b3VsZCBiZSBtb3N0bHkgdmFyaWVkIGJhc2VkIG9uIG51bWJlciBvZiBnZW5lcyB0aGF0IHRoZXkgYXJlIGV4cHJlc3NpbmcuIEluIHRoZSBjZWxsLWN5Y2xlIHBsb3Qgc2hvd2VkIGVhcmxpZXIsIHdlIHNob3dlZCB0aGF0IHRoZXJlIGlzIGNlbGwtY3ljbGUgZWZmZWN0IGJ1dCBpdCBtaWdodCBub3QgYmUgY29uZm91bmRpbmcgdGhlIGFuYWx5c2lzLiAKCk51bWJlciBvZiB0b3RhbCBmZWF0dXJlcyBleHBsYWluaW5nIG1ham9yaXR5IG9mIHRoZSB2YXJpYWJpbGl0eSBpcyBhIGNvbW1vbiBpc3N1ZSBpbiBzaW5nbGUtY2VsbCBSTkEtc2VxLiBUaGlzIGNvdWxkIGJlIGR1ZSB0byB0aGUgc3BhcnNpdHkgb2YgdGhlIGRhdGEuIFdlIG5lZWQgdG8ga2VlcCBpbiBtaW5kIHRoYXQgY2VsbHMgaW4gdGhlIFBDQSBwbG90IG1pZ2h0IGJlIHNlcGVyYXRpbmcganVzdCBiZWNhdXNlIHRoZXkgaGF2ZSB2ZXJ5IGRpZmZlcmVudCBudW1iZXIgb2YgZmVhdHVyZXMgZXhwcmVzc2VkIGluIHRvdGFsLgoKSG93ZXZlciwgSSBhbSBleHBlY3RpbmcgdGhpcyB3b3VsZCBub3QgY29uZm91bmQgdGhlIHQtU05FIHBsb3QgdGhhdCB3b3VsZCB0YWtlIHRoZSBub24tbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMuCgoKIyBJZGVudGlmeWluZyBIVkdzIGZyb20gdGhlIG5vcm1hbGl6ZWQgbG9nLWV4cHJlc3Npb24KClRoaXMgaXMgb25lIG9mIHRoZSB2ZXJ5IGltcG9ydGFudCBleGNlcmNpZXMuCgpJIG5vdyBpZGVudGlmeSBIVkdzIHRvIGZvY3VzIG9uIHRoZSBnZW5lcyB0aGF0IGFyZSBkcml2aW5nIGhldGVyb2dlbmVpdHkgYWNyb3NzIHRoZSBwb3B1bGF0aW9uIG9mIGNlbGxzLiBUaGlzIHJlcXVpcmVzIGVzdGltYXRpb24gb2YgdGhlIHZhcmlhbmNlIGluIGV4cHJlc3Npb24gZm9yIGVhY2ggZ2VuZSwgZm9sbG93ZWQgYnkgZGVjb21wb3NpdGlvbiBvZiB0aGUgdmFyaWFuY2UgaW50byBiaW9sb2dpY2FsIGFuZCB0ZWNobmljYWwgY29tcG9uZW50cy4gSFZHcyBhcmUgdGhlbiBpZGVudGlmaWVkIGFzIHRob3NlIGdlbmVzIHdpdGggdGhlIGxhcmdlc3QgYmlvbG9naWNhbCBjb21wb25lbnRzLiBUaGlzIGF2b2lkcyBwcmlvcml0aXppbmcgZ2VuZXMgdGhhdCBhcmUgaGlnaGx5IHZhcmlhYmxlIGR1ZSB0byB0ZWNobmljYWwgZmFjdG9ycywgc3VjaCBhcyBzYW1wbGluZyBub2lzZSBkdXJpbmcgUk5BIGNhcHR1cmUgYW5kIGxpYnJhcnkgcHJlcGFyYXRpb24uCgpJbiB0aGUgcmVjZW50IGltcGxlbWVudGF0aW9uIG9mIGBzZXVyYXRgLCBSYWh1bCBTYXRpamEgdG9vayBhIHNsaWdodGx5IGRpZmZlcmVudCBhcHByb2FjaCBmb3IgSFZHIGNhbGN1bGF0aW9uLiBUaGV5IGNhbGN1bGF0ZSB0aGUgdmFyaWFuY2UgYW5kIG1lYW4gZm9yIGVhY2ggZ2VuZSBpbiB0aGUgZGF0YXNldCAoc3RvcmluZyB0aGlzIGluIGBvYmplY3RAaHZnLmluZm9gKSwgYW5kIHNvcnRzIGdlbmVzIGJ5IHRoZWlyIHZhcmlhbmNlL21lYW4gcmF0aW8gKFZNUikuIFRoZXkgaGF2ZSBvYnNlcnZlZCB0aGF0IGZvciBsYXJnZS1jZWxsIGRhdGFzZXRzIHdpdGggdW5pcXVlIG1vbGVjdWxhciBpZGVudGlmaWVycywgc2VsZWN0aW5nIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyAoSFZHKSBzaW1wbHkgYmFzZWQgb24gVk1SIGlzIGFuIGVmZmljaWVudCBhbmQgcm9idXN0IHN0cmF0ZWd5LiAKCkJ1dCBpbiBvdXIgaW1wbGVtZW50YXRpb24gd2UgZml0IHRoZSB0cmVuZCB0byB0aGUgdmFyaWFuY2UgZXN0aW1hdGVzIG9mIHRoZSBlbmRvZ2Vub3VzIGdlbmVzLCB1c2luZyB0aGUgYHVzZS5zcGlrZXM9RkFMU0VgIHNldHRpbmcgYXMgc2hvd24gYmVsb3cuIFRoaXMgYXNzdW1lcyB0aGF0IHRoZSBtYWpvcml0eSBvZiBnZW5lcyBhcmUgbm90IHZhcmlhYmx5IGV4cHJlc3NlZCwgc3VjaCB0aGF0IHRoZSB0ZWNobmljYWwgY29tcG9uZW50IGRvbWluYXRlcyB0aGUgdG90YWwgdmFyaWFuY2UgZm9yIHRob3NlIGdlbmVzLiBUaGUgZml0dGVkIHZhbHVlIG9mIHRoZSB0cmVuZCBpcyB0aGVuIHVzZWQgYXMgYW4gZXN0aW1hdGUgb2YgdGhlIHRlY2huaWNhbCBjb21wb25lbnQuIAoKX19zcGFuOl9fIExvdy1hYnVuZGFuY2UgZ2VuZXMgd2l0aCBtZWFuIGxvZy1leHByZXNzaW9uIGJlbG93IGBtaW4ubWVhbmAgYXJlIG5vdCB1c2VkIGluIHRyZW5kIGZpdHRpbmcsIHRvIHByZXNlcnZlIHRoZSBzZW5zaXRpdml0eSBvZiBzcGFuLWJhc2VkIHNtb290aGVycyBhdCBtb2RlcmF0ZS10by1oaWdoIGFidW5kYW5jZXMuIEl0IGFsc28gcHJvdGVjdHMgYWdhaW5zdCBkaXNjcmV0ZW5lc3MsIHdoaWNoIGNhbiBpbnRlcmZlcmUgd2l0aCBlc3RpbWF0aW9uIG9mIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgdmFyaWFuY2UgZXN0aW1hdGVzIGFuZCBhY2N1cmF0ZSBzY2FsaW5nIG9mIHRoZSB0cmVuZC4gVGhlIGRlZmF1bHQgdGhyZXNob2xkIGlzIGNob3NlbiBiYXNlZCBvbiB0aGUgcG9pbnQgYXQgd2hpY2ggZGlzY3JldGVuZXNzIGlzIG9ic2VydmVkIGluIHZhcmlhbmNlIGVzdGltYXRlcyBmcm9tIFBvaXNzb24tZGlzdHJpYnV0ZWQgY291bnRzLiBGb3IgaGV0ZXJvZ2VuZW91cyBkcm9wbGV0IGRhdGEsIGEgbG93ZXIgdGhyZXNob2xkIG9mIDAuMDAxLTAuMDEgc2hvdWxkIGJlIHVzZWQuCgpgYGB7cn0KdmFyLmZpdCA8LSB0cmVuZFZhcihjZFNjRmlsdEFubm90LCBtZXRob2Q9ImxvZXNzIiwgdXNlLnNwaWtlcz1GQUxTRSwgbWluLm1lYW49MS4wKQp2YXIub3V0IDwtIGRlY29tcG9zZVZhcihjZFNjRmlsdEFubm90LCB2YXIuZml0KQpgYGAKCldlIGFzc2VzcyB0aGUgc3VpdGFiaWxpdHkgb2YgdGhlIHRyZW5kIGZpdHRlZCB0byB0aGUgZW5kb2dlbm91cyB2YXJpYW5jZXMgYnkgZXhhbWluaW5nIHdoZXRoZXIgaXQgaXMgY29uc2lzdGVudCB3aXRoIHRoZSB2YXJpYW5jZXMuIFRoZSB0cmVuZCBwYXNzZXMgdGhyb3VnaCBvciBjbG9zZSB0byBtb3N0IG9mIHRoZSBlbmRvZ2Vub3VzIGdlbmUgdmFyaWFuY2VzLCBpbmRpY2F0aW5nIHRoYXQgb3VyIGFzc3VtcHRpb24gKHRoYXQgbW9zdCBnZW5lcyBoYXZlIGxvdyBsZXZlbHMgb2YgYmlvbG9naWNhbCB2YXJpYWJpbGl0eSkgaXMgdmFsaWQuIFRoaXMgc3RyYXRlZ3kgZXhwbG9pdHMgdGhlIGxhcmdlIG51bWJlciBvZiBlbmRvZ2Vub3VzIGdlbmVzIHRvIG9idGFpbiBhIHN0YWJsZSB0cmVuZC4gCgpgYGB7cn0KcGxvdCh2YXIub3V0JG1lYW4sIHZhci5vdXQkdG90YWwsIHBjaD0xNiwgY2V4PTAuNiwgeGxhYj0iTWVhbiBsb2ctZXhwcmVzc2lvbiIsIHlsYWI9IlZhcmlhbmNlIG9mIGxvZy1leHByZXNzaW9uIikKbyA8LSBvcmRlcih2YXIub3V0JG1lYW4pCmxpbmVzKHZhci5vdXQkbWVhbltvXSwgdmFyLm91dCR0ZWNoW29dLCBjb2w9ImRvZGdlcmJsdWUiLCBsd2Q9MikKYGBgCklkZWFsbHkgdGhlIHBsb3QgYWJvdmUgd291bGQgbG9vayBsaWtlIHRoZSBtb3VudGFpbiwgd2hlcmUgaXQgd291bGQgaGF2ZSByaXNlIGluIHRoZSBtaWRkbGUgYnV0IHRoZW4gd291bGQgYmUgZHJvcHBpbmcgYXQgdGhlIGVuZCBhcyB3ZSBoYXZlIGxvdyB2YXJpYW5jZSBmb3IgaGlnaGx5IGV4cHJlc3NlZCBnZW5lcy4gVGhlIHRyZW5kIGxpbmUgd291bGQgZm9sbG93IGl0IGFzIHdlbGwuIAo8IS0tSG93ZXZlciwgZm9yIHRoaXMgZGF0YXNldCB0aGUgbW91bnRhaW4gc2hhcGUgaXMgbm90IG9idmlvdXMgYW5kIHRoaXMgY291bGQgYmUgZHVlIHRvIHVzZSBvZiBVTUkgY291bnRzLi0tPgoKSFZHcyBhcmUgZGVmaW5lZCBhcyBnZW5lcyB3aXRoIGJpb2xvZ2ljYWwgY29tcG9uZW50cyB0aGF0IGFyZSBzaWduaWZpY2FudGx5IGdyZWF0ZXIgdGhhbiB6ZXJvIGF0IGEgZmFsc2UgZGlzY292ZXJ5IHJhdGUgKEZEUikgb2YgNSUuIFRoZXNlIGdlbmVzIGFyZSBpbnRlcmVzdGluZyBhcyB0aGV5IGRyaXZlIGRpZmZlcmVuY2VzIGluIHRoZSBleHByZXNzaW9uIHByb2ZpbGVzIGJldHdlZW4gY2VsbHMsIGFuZCBzaG91bGQgYmUgcHJpb3JpdGl6ZWQgZm9yIGZ1cnRoZXIgaW52ZXN0aWdhdGlvbi4gSW4gYWRkaXRpb24sIEkgb25seSBjb25zaWRlciBhIGdlbmUgdG8gYmUgYSBIVkcgaWYgaXQgaGFzIGEgYmlvbG9naWNhbCBjb21wb25lbnQgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDAuNS4gRm9yIHRyYW5zZm9ybWVkIGV4cHJlc3Npb24gdmFsdWVzIG9uIHRoZSBsb2cyIHNjYWxlLCB0aGlzIG1lYW5zIHRoYXQgdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZSBpbiB0cnVlIGV4cHJlc3Npb24gYmV0d2VlbiBhbnkgdHdvIGNlbGxzIHdpbGwgYmUgYXQgbGVhc3QgMi1mb2xkLiAoVGhpcyByZWFzb25pbmcgYXNzdW1lcyB0aGF0IHRoZSB0cnVlIGxvZy1leHByZXNzaW9uIHZhbHVlcyBhcmUgTm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCB2YXJpYW5jZSBvZiAwLjUuIFRoZSByb290LW1lYW4tc3F1YXJlIG9mIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdHdvIHZhbHVlcyBpcyB0cmVhdGVkIGFzIHRoZSBhdmVyYWdlIGxvZzItZm9sZCBjaGFuZ2UgYmV0d2VlbiBjZWxscyBhbmQgaXMgZXF1YWwgdG8gdW5pdHkuKSBJIHJhbmsgdGhlIHJlc3VsdHMgYnkgdGhlIGJpb2xvZ2ljYWwgY29tcG9uZW50IHRvIGZvY3VzIG9uIGdlbmVzIHdpdGggbGFyZ2VyIGJpb2xvZ2ljYWwgdmFyaWFiaWxpdHkuCgpgYGB7cn0KaHZnLm91dCA8LSB2YXIub3V0W3doaWNoKHZhci5vdXQkRkRSIDw9IDAuMDUgJiB2YXIub3V0JGJpbyA+PSAwLjUpLF0KaHZnLm91dCA8LSBodmcub3V0W29yZGVyKGh2Zy5vdXQkYmlvLCBkZWNyZWFzaW5nPVRSVUUpLF0KbnJvdyhodmcub3V0KQpgYGAKU28sIHRoZXJlIGFyZSA2MzIgSFZHcyBmb3IgdGhpcyBkYXRhc2V0LgoKUGxvdHRpbmcgdGhlIEhWR3MuIFRoZSByZWQgZG90cyBhcmUgdGhlIEhWR3Mgd2Ugc2VsZWN0ZWQuIAoKYGBge3J9CnBsb3QodmFyLm91dCRtZWFuLCB2YXIub3V0JHRvdGFsLCBwY2g9MTYsIGNleD0wLjYsIHhsYWI9Ik1lYW4gbG9nLWV4cHJlc3Npb24iLAogICAgIHlsYWI9IlZhcmlhbmNlIG9mIGxvZy1leHByZXNzaW9uIikKbyA8LSBvcmRlcih2YXIub3V0JG1lYW4pCmxpbmVzKHZhci5vdXQkbWVhbltvXSwgdmFyLm91dCR0ZWNoW29dLCBjb2w9ImRvZGdlcmJsdWUiLCBsd2Q9MikKcG9pbnRzKHZhci5vdXRbcm93bmFtZXMoaHZnLm91dCksXSRtZWFuLCB2YXIub3V0W3Jvd25hbWVzKGh2Zy5vdXQpLF0kdG90YWwsIGNvbD0icmVkIiwgcGNoPTE2LCBjZXg9MC42KQpgYGAKClBsb3R0aW5nIHRoZSB0b3AgMTUgSFZHczoKYGBge3IgZmlnRXhwLCBmaWcud2lkdGg9OX0KcGxvdEV4cHJlc3Npb24oY2RTY0ZpbHRBbm5vdCwgcm93bmFtZXMoaHZnLm91dClbMToxNV0pCmBgYAoKX19EaXNjdXNzIG1lYW5pbmcgb2YgZ2VuZXMuX18KCgpgYGB7cn0Kd3JpdGUudGFibGUoZmlsZT0iSFZHX0xvdWlzYV9OZWxzb25fMTBYX0RhdGFzZXQudHN2IiwgaHZnLm91dCwgc2VwPSJcdCIsIHF1b3RlID0gRkFMU0UsIGNvbC5uYW1lcyA9IE5BKQpoZWFkKGh2Zy5vdXQsIDIwKQpgYGAKVGhlcmUgYXJlIG90aGVyIGFsdGVybmF0aXZlIGFwcHJvYWNoZXMgZm9yIGRldGVybWluaW5nIHRoZSBIVkdzIHNwZWNpYWxseSB0aG9zZSBiYXNlZCBvbiBDb2VmZmljaWVudCBvZiBWYXJpYW5jZS4gSGVyZSBJIHVzZSB0aGUgdmFyaWFuY2Ugb2YgdGhlIGxvZy1leHByZXNzaW9uIHZhbHVlcyBiZWNhdXNlIHRoZSBsb2ctdHJhbnNmb3JtYXRpb24gcHJvdGVjdHMgYWdhaW5zdCBnZW5lcyB3aXRoIHN0cm9uZyBleHByZXNzaW9uIGluIG9ubHkgb25lIG9yIHR3byBjZWxscy4gVGhpcyBlbnN1cmVzIHRoYXQgdGhlIHNldCBvZiB0b3AgSFZHcyBpcyBub3QgZG9taW5hdGVkIGJ5IGdlbmVzIHdpdGggKG1vc3RseSB1bmludGVyZXN0aW5nKSBvdXRsaWVyIGV4cHJlc3Npb24gcGF0dGVybnMuCgpIb3dldmVyLCBJIHdvdWxkIGxpa2UgdG8gYWxzbyB0ZXN0IG90aGVyIG1ldGhvZHMgb2YgaWRlbnRpZnlpbmcgdGhlIEhWR3MuIEl0IGhhcyBiZWVuIG1lbnRpb25lZCB0aGF0IGZpdHRpbmcgdGhlIHRyZW5kbGluZSB0byBlbmRvZ2Vub3VzIGdlbmVzIG1pZ2h0IG5vdCBhbHdheXMgYmUgYSBnb29kIGlkZWEuCgoKIyMgRG93bnN0cmVhbSBhbmFseXNpcyB3aXRoIEhWRyBnZW5lcwpJIHdpbGwgc2VlIGluc3RlYWQgb2YgdGFraW5nIHRoZSBjb3JyZWxhdGVkIGdlbmVzIGhvdyB0aGUgcmVzdWx0IGNvbWVzIHVwIHdpdGggdGhlIEhWR3Mgb25seS4KCmBgYHtyIG11bHRpUENBSFZHLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTJ9CnJvd1ZhcnNTb3J0ZWQgPC0gZXhwcnMoY2RTY0ZpbHRBbm5vdClbcm93bmFtZXMoaHZnLm91dCksXQpGaW5hbFBDQURhdGEgPC0gdChyb3dWYXJzU29ydGVkKQpwY2FQUkNvbXAgPC0gcHJjb21wKEZpbmFsUENBRGF0YSkKbm1heCA9IDEwCmlmKGRpbShwY2FQUkNvbXAkeClbMV0gPCAxMCl7IG5tYXggPSBkaW0ocGNhUFJDb21wJHgpWzFdIH0KdHh0MSA8LSBwYXN0ZSgiUGVyY2VudF9QQ19WYXJfb25maXJzdCIsbm1heCwiUENzIixzZXA9IiIpCnBjYV92YXIgPSBwY2FQUkNvbXAkc2RldiBeIDIKcGNhX3Zhcl9wZXJjZW50IDwtIDEwMCAqIHBjYV92YXIgLyBzdW0ocGNhX3ZhcikKcGNhX3Zhcl9wZXJjZW50X2ZpcnN0MTAgPC0gTkEgKiBwY2FfdmFyCnBjYV92YXJfcGVyY2VudF9maXJzdDEwWzE6bm1heF0gPC0gMTAwICogcGNhX3ZhclsxOm5tYXhdIC8gc3VtKHBjYV92YXJbMTpubWF4XSkKCiNwY2FfY29ycl9yZWFkcyA8LSBhcHBseShwY2FQUkNvbXAkeCwyLGZ1bmN0aW9uKHgpIGNvcih4LHJlcG9ydF9zdWIkQXNzaWduZWQpKQogICAgCnBjYV92YXJfb3V0IDwtIGRhdGEuZnJhbWUocm91bmQocGNhX3ZhciwzKSxyb3VuZChwY2FfdmFyX3BlcmNlbnQsMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQocGNhX3Zhcl9wZXJjZW50X2ZpcnN0MTAsMSkpCiNyb3duYW1lcyhwY2FfdmFyX291dCkgPC0gcm93bmFtZXMocGNhUFJDb21wJHgpCmNvbG5hbWVzKHBjYV92YXJfb3V0KSA8LSBjKCJQQ19WYXIiLCJQQ19WYXJfcGVyY2VudCIsdHh0MSkKbkNvbFRvRGlzcGxheSA9IDUKZGYgPC0gYXMuZGF0YS5mcmFtZShwY2FQUkNvbXAkeCkKZGYkQ2VsbD1hcy5mYWN0b3IoY29sRGF0YShjZFNjRmlsdEFubm90KSRTYW1wbGUpCnAgPC0gZ2dwYWlycyhkZiwgY29sdW1ucz0xOm5Db2xUb0Rpc3BsYXksIHVwcGVyPWxpc3QoY29udGludW91cz0icG9pbnRzIiksIAogICAgICAgICAgICAgdGl0bGU9J1Bsb3R0aW5nIGZpcnN0IGZvdXIgUENBcycsIAogICAgICAgICAgICAgbWFwcGluZyA9IGFlc19zdHJpbmcoY29sb3I9IkNlbGwiKSwKICAgICAgICAgICAgIGxlZ2VuZCA9IGMoMSxuQ29sVG9EaXNwbGF5KSwKICAgICAgICAgICAgIGNvbHVtbkxhYmVscyA9IGFzLmNoYXJhY3RlcihwYXN0ZTAoY29sbmFtZXMoZGZbLDE6bkNvbFRvRGlzcGxheV0pLCAnIDogJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjYV92YXJfb3V0JFBDX1Zhcl9wZXJjZW50WzE6bkNvbFRvRGlzcGxheV0sICclIHZhcmlhbmNlJykpKSsKICAgIHRoZW1lX2xpZ2h0KGJhc2Vfc2l6ZT0xNSkrICAgICAKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKZm9yKGkgaW4gMTpwJG5yb3cpIHsKICBmb3IoaiBpbiAxOnAkbmNvbCl7CiAgICBwW2ksal0gPC0gcFtpLGpdICsgCiAgICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgKwogICAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgIAogIH0KfQogICAgCnByaW50KHApCmBgYAoKVGhlIHJlc3VsdHMgZG9lcyBub3QgZG8gbXVjaCB3aXRoIHRoZSBjaG9zZW4gYW5kIHRoZSBIVkcgb25lcy4gU28gZm9yIHRoZSBkb3duc3RyZWFtIGFuYWx5c2lzIEkgd2lsbCB0YWtlIHRoZSBIVkcgZ2VuZXMgb25seSBpbnN0ZWFkIG9mIHRoZSBjb3JyZWxhdGVkIEhWR3MuCgoKCiMjIyB0LVNORSBwbG90IHdpdGggb25seSB0aGUgSFZHIGdlbmVzOgpgYGB7ciBmaWdUc25lTTNEcm9wR2VuZXMsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTZ9CnRzbmVfb3V0IDwtIFJ0c25lKGFzLm1hdHJpeChwY2FQUkNvbXAkeFssMToxNF0pLGNoZWNrX2R1cGxpY2F0ZXMgPSBGQUxTRSwgcGNhID0gRkFMU0UsCiAgICAgICAgICAgICBwZXJwbGV4aXR5PTMwLCB0aGV0YT0wLjAxLCBkaW1zPTIsIG51bV90aHJlYWRzID0gOCkKClJlcCA8LSBhcy5mYWN0b3IoY29sRGF0YShjZFNjRmlsdEFubm90KSRTYW1wbGUpCgoKY291bnRzIDwtIGNvbERhdGEoY2RTY0ZpbHRBbm5vdCkkdG90YWxfY291bnRzCnAgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSksIGFlcyh4PVYxLCB5PVYyLCBjb2xvcj1jb3VudHMpKSArCiAgICAgZ2VvbV9wb2ludChzaXplPTAuNzUpICsKICAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0wLjUpKSkgKwogICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgIGdndGl0bGUoInQtU05FIDJEIEVtYmVkZGluZyBvZiBTYW1wbGUgRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpCgpwMiA8LSBnZ3Bsb3QoYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKSwgYWVzKHg9VjEsIHk9VjIsIGNvbG9yPVJlcCkpICsKICAgICBnZW9tX3BvaW50KHNpemU9MC43NSkgKwogICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTAuOCkpKSArCiAgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgICAgZ2d0aXRsZSgidC1TTkUgMkQgRW1iZWRkaW5nIG9mIEV4cHJlc3Npb24gRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xMCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKQpwMgoKI211bHRpcGxvdChwLHAyLGNvbHM9MikKYGBgCgpgYGB7cn0Kc2F2ZS5pbWFnZSgpCmBgYAoKU28gdGhlIHQtU05lIHBsb3QgY2xlYXJseSBzZXBlcmF0ZXMgdGhlIHBhdGllbnRzLiBUaGlzIGlzIHdoYXQgd2UgZ2VuZXJhbGx5IHNlZSB3aGVuIHdlIGhhdmUgbXVsdGlwbGUgcGF0aWVudHMuIFRoZSBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZSBmb3IgZXZlcnkgcGF0aWVudCBpdHNlbGYgaXMgdmVyeSBkaWZmZXJlbnQgZm9yIGNlbGxzIGFuZCB0aGF0IGlzIHdoeSBjZWxscyBmcm9tIGRpZmZlcmVudCBwYXRpZW50cyBjb21lIGRpZmZlcmVudGx5LiBPbmUgaW50ZXJlc3RpbmcgdGhpbmcgaXMgYSBjbHVzdGVyIHdpdGggYWxsIHRoZSBjZWxscyBtaXhlZC4gTm93LCB0aGlzIGNvdWxkIGJlIHRoZSAyNSUgb2Ygc3Ryb21hbCBjZWxscyBmcm9tIGVhY2ggcGF0aWVudCB3aGVyZSBhcyB0aGUgaGV0ZXJvZ2VuZWl0eSBpbiB0dW1vdXIgc2FtcGxlcyBzZXBlcmF0ZSB0aGUgcGF0aWVudHMuIAojIyMgUnVubmluZyBVbWFwIGZvciBkYXRhIHZpc3VhbGl6YXRpb24KCmBgYHtyfQplbWJlZGRpbmcgPC0gdW1hcChwY2FQUkNvbXAkeFssMToxNF0pCmBgYApgYGB7ciBmaWdVbWFwLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD02fQphcy50aWJibGUoZW1iZWRkaW5nJGxheW91dCkgJT4lCiAgbXV0YXRlKFNhbXBsZXMgPSBjb2xEYXRhKGNkU2NGaWx0QW5ub3QpJFNhbXBsZSkgJT4lCiAgZ2dwbG90KGFlcyhWMSwgVjIsIGNvbG9yPVNhbXBsZXMpKSArICBnZW9tX3BvaW50KHNpemU9MC43NSkgKyB0aGVtZV9jbGFzc2ljKCkgK3NjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWNfc2FtcGxlX2NvbCkgCmBgYAoKCgojIENsdXN0ZXJpbmcKVGhlcmUgYXJlIG51bWVyb3VzIG1ldGhvZHMgb2YgY2x1c3RlcmluZywgdGhlIGR5bmFtaWMtY3V0LXRyZWUsIHRoZSBrLW1lYW4sIGstbWVkb2lkcywgR01NLCBHUC1MVk0gYW5kIHNvIG9uLiBJIHdpbGwgc3RyYWlnaHQganVtcCBpbnRvIGR5bmFtaWMtY3V0LXRyZWUgYXMgdGhpcyBpcyB0aGUgb25lIHRoYXQgcGVyZm9ybWVkIGJlc3Qgd2hpbGUgSSB3YXMgZG9pbmcgYW5hbHlzaXMgd2l0aCB0aGUgbmFpdmUgYW5kIEFzcGQxMiBkYXRhc2V0LgoKIyMgRHluYW1pYyBDdXQgVHJlZQpIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBpcyBhIHdpZGVseSB1c2VkIG1ldGhvZCBmb3IgZGV0ZWN0aW5nIGNsdXN0ZXJzIGluIGdlbm9taWMgZGF0YS4gQ2x1c3RlcnMgYXJlIGRlZmluZWQgYnkgY3V0dGluZyBicmFuY2hlcyBvZmYgdGhlIGRlbmRyb2dyYW0uIEEgY29tbW9uIGJ1dCBpbmZsZXhpYmxlIG1ldGhvZCB1c2VzIGEgY29uc3RhbnQgaGVpZ2h0IGN1dG9mZiB2YWx1ZTsgdGhpcyBtZXRob2QgZXhoaWJpdHMgc3Vib3B0aW1hbCBwZXJmb3JtYW5jZSBvbiBjb21wbGljYXRlZCBkZW5kcm9ncmFtcy4gV2UgcHJlc2VudCB0aGUgRHluYW1pYyBUcmVlIEN1dCBSIGxpYnJhcnkgdGhhdCBpbXBsZW1lbnRzIG5vdmVsIGR5bmFtaWMgYnJhbmNoIGN1dHRpbmcgbWV0aG9kcyBmb3IgZGV0ZWN0aW5nIGNsdXN0ZXJzIGluIGEgZGVuZHJvZ3JhbSBkZXBlbmRpbmcgb24gdGhlaXIgc2hhcGUuIENvbXBhcmVkIHRvIHRoZSBjb25zdGFudCBoZWlnaHQgY3V0b2ZmIG1ldGhvZCwgb3VyIHRlY2huaXF1ZXMgb2ZmZXIgdGhlIGZvbGxvd2luZyBhZHZhbnRhZ2VzOiAoMSkgdGhleSBhcmUgY2FwYWJsZSBvZiBpZGVudGlmeWluZyBuZXN0ZWQgY2x1c3RlcnM7ICgyKSB0aGV5IGFyZSBmbGV4aWJsZSAtLS0gY2x1c3RlciBzaGFwZSBwYXJhbWV0ZXJzIGNhbiBiZSB0dW5lZCB0byBzdWl0IHRoZSBhcHBsaWNhdGlvbiBhdCBoYW5kOyAoMykgdGhleSBhcmUgc3VpdGFibGUgZm9yIGF1dG9tYXRpb247IGFuZCAoNCkgdGhleSBjYW4gb3B0aW9uYWxseSBjb21iaW5lIHRoZSBhZHZhbnRhZ2VzIG9mIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGFuZCBwYXJ0aXRpb25pbmcgYXJvdW5kIG1lZG9pZHMsIGdpdmluZyBiZXR0ZXIgZGV0ZWN0aW9uIG9mIG91dGxpZXJzLgoKIyMjIENsdXN0ZXJpbmcgY2VsbHMgaW50byBwdXRhdGl2ZSBzdWJwb3B1bGF0aW9ucwpXZSBwZXJmb3JtIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9uIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2VzIGJldHdlZW4gY2VsbHMsIHVzaW5nIFdhcmTigJlzIGNyaXRlcmlvbiB0byBtaW5pbWl6ZSB0aGUgdG90YWwgdmFyaWFuY2Ugd2l0aGluIGVhY2ggY2x1c3Rlci4gVGhpcyB5aWVsZHMgYSBkZW5kcm9ncmFtIHRoYXQgZ3JvdXBzIHRvZ2V0aGVyIGNlbGxzIHdpdGggc2ltaWxhciBleHByZXNzaW9uIHBhdHRlcm5zIGFjcm9zcyB0aGUgY2hvc2VuIGdlbmVzLiBBbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCBpcyB0byBjbHVzdGVyIG9uIGEgbWF0cml4IG9mIGRpc3RhbmNlcyBkZXJpdmVkIGZyb20gY29ycmVsYXRpb25zIChlLmcuLCBhcyBpbiBgcXVpY2tDbHVzdGVyYCkuIFRoaXMgaXMgbW9yZSByb2J1c3QgdG8gbm9pc2UgYW5kIG5vcm1hbGl6YXRpb24gZXJyb3JzLCBidXQgaXMgYWxzbyBsZXNzIHNlbnNpdGl2ZSB0byBzdWJ0bGUgY2hhbmdlcyBpbiB0aGUgZXhwcmVzc2lvbiBwcm9maWxlcy4KCmBgYHtyfQpjaG9zZW4uZXhwcnMgPC0gbG9nY291bnRzKGNkU2NGaWx0QW5ub3Rbcm93bmFtZXMoaHZnLm91dCksXSkKbXkuZGlzdCA8LSBkaXN0KHQoY2hvc2VuLmV4cHJzKSkKbXkudHJlZSA8LSBoY2x1c3QobXkuZGlzdCwgbWV0aG9kID0gIndhcmQuRDIiKQpgYGAKCkNsdXN0ZXJzIGFyZSBleHBsaWNpdGx5IGRlZmluZWQgYnkgYXBwbHlpbmcgYSBkeW5hbWljIHRyZWUgY3V0IChMYW5nZmVsZGVyIGV0IGFsLiwgMjAwOClbaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb2luZm9ybWF0aWNzL2FydGljbGUvMjQvNS83MTkvMjAwNzUxXSB0byB0aGUgZGVuZHJvZ3JhbS4gVGhpcyBleHBsb2l0cyB0aGUgc2hhcGUgb2YgdGhlIGJyYW5jaGVzIGluIHRoZSBkZW5kcm9ncmFtIHRvIHJlZmluZSB0aGUgY2x1c3RlciBkZWZpbml0aW9ucywgYW5kIGlzIG1vcmUgYXBwcm9wcmlhdGUgdGhhbiBjdXRyZWUgZm9yIGNvbXBsZXggZGVuZHJvZ3JhbXMuIEdyZWF0ZXIgY29udHJvbCBvZiB0aGUgZW1waXJpY2FsIGNsdXN0ZXJzIGNhbiBiZSBvYnRhaW5lZCBieSBtYW51YWxseSBzcGVjaWZ5aW5nIGN1dEhlaWdodCBpbiBjdXRyZWVEeW5hbWljLgoKYGBge3J9CmxpYnJhcnkoZHluYW1pY1RyZWVDdXQpCm15LmNsdXN0ZXJzIDwtIHVubmFtZShjdXRyZWVEeW5hbWljKG15LnRyZWUsIGRpc3RNPWFzLm1hdHJpeChteS5kaXN0KSwgdmVyYm9zZT0wKSkKYGBgCgpOdW1iZXIgb2YgY2x1c3RlcnMgY2hvc2VuIGJ5IHRoaXMgbWV0aG9kIGlzOgpgYGB7cn0KbGV2ZWxzKGFzLmZhY3RvcihteS5jbHVzdGVycykpCmBgYApgYGB7cn0KY2RTY0ZpbHRBbm5vdCRDbHVzdGVycyA8LSBhcy5mYWN0b3IobXkuY2x1c3RlcnMpCmBgYAoKRHJhd2luZyB0aGUgaGVhdG1hcCwgZmlyc3Qgc2NhbGUgdGU6IApgYGB7ciBmaWdIZWF0Q2x1c3QsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD04fQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQpoZWF0LnZhbHMgPC0gY2hvc2VuLmV4cHJzIC0gcm93TWVhbnMoY2hvc2VuLmV4cHJzKQoKI2NsdXN0LmNvbCA8LSByYWluYm93KG1heChteS5jbHVzdGVycykpCiNoZWF0bWFwLjIoaGVhdC52YWxzLCBjb2w9Ymx1ZXJlZCwgc3ltYnJlYWs9VFJVRSwgdHJhY2U9J25vbmUnLCBjZXhSb3c9MC4zLAojICAgIENvbFNpZGVDb2xvcnM9Y2x1c3QuY29sW215LmNsdXN0ZXJzXSwgQ29sdj1hcy5kZW5kcm9ncmFtKG15LnRyZWUpKQoKZGYgPSBkYXRhLmZyYW1lKENsYXNzID0gYXMuZmFjdG9yKG15LmNsdXN0ZXJzKSkKaGEgPSBIZWF0bWFwQW5ub3RhdGlvbihkZiA9IGRmKQojaGEgPSBIZWF0bWFwQW5ub3RhdGlvbihkZiA9IGRmLCBjb2wgPSBsaXN0KENvbmRpdGlvbiA9IGMoIk1DX0EiID0gICJkb2RnZXJibHVlIiwgIk1DX0IiPSAiZmlyZWJyaWNrIiwgIk1DX0MiPSJmb3Jlc3RncmVlbiIsICJNQ19EIj0iZ29sZCIpKSkKSGVhdG1hcChoZWF0LnZhbHMgLCB0b3BfYW5ub3RhdGlvbiA9IGhhLCBzaG93X2NvbHVtbl9uYW1lcz1GQUxTRSwgY2x1c3Rlcl9yb3dzID0gVFJVRSwgY2x1c3RlcmluZ19tZXRob2RfY29sdW1ucyA9ICJ3YXJkLkQyIiwgcm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDgpKQoKYGBgCgpTYXZpbmcgdGhlIGhlYXRtYXAgZm9yIGxhdGVyIGV4cGxvcmF0aW9uOgpgYGB7cn0KcGRmKCJNYXR0aGV3X0hlcHdvcnRoX2hlYXRfbmV3RGF0YXNldC5wZGYiLCB3aWR0aD0yMCwgaGVpZ2h0PTQwKQpIZWF0bWFwKGhlYXQudmFscyAsIHRvcF9hbm5vdGF0aW9uID0gaGEsIHNob3dfY29sdW1uX25hbWVzPUZBTFNFLCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCBjbHVzdGVyaW5nX21ldGhvZF9jb2x1bW5zID0gIndhcmQuRDIiLCByb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gOCkpCmRldi5vZmYoKQpgYGAKCnQtU05FIHdpdGggY2x1c3RlciBhbGxvY2F0aW9uOgpgYGB7ciBmaWd0c25lQ2x1c3RlckFsbG9jLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD02fQoKQ2x1c3RlcnMgPC0gYXMuZmFjdG9yKG15LmNsdXN0ZXJzKQoKcDIgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSksIGFlcyh4PVYxLCB5PVYyLCBjb2xvcj1DbHVzdGVycykpICsKICAgICBnZW9tX3BvaW50KHNpemU9MS4wKSArCiAgICAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MSkpKSArCiAgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgICAgZ2d0aXRsZSgidC1TTkUgMkQgRW1iZWRkaW5nIG9mIGNsdXN0ZXIgRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xMCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jX2NsdXN0X2NvbCkKcDIKCiNtdWx0aXBsb3QocCxwMixjb2xzPTIpCmBgYAoKYGBge3J9CkNsdXN0ZXJzIDwtIGFzLmZhY3RvcihteS5jbHVzdGVycykKYXMudGliYmxlKGVtYmVkZGluZyRsYXlvdXQpICU+JQogIG11dGF0ZShDbHVzdGVycyA9IENsdXN0ZXJzKSAlPiUKICBnZ3Bsb3QoYWVzKFYxLCBWMiwgY29sb3I9Q2x1c3RlcnMpKSArICBnZW9tX3BvaW50KHNpemU9MC43NSkgKyB0aGVtZV9jbGFzc2ljKCkgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNfY2x1c3RfY29sKQpgYGAKYGBge3J9CmFzLnRpYmJsZShlbWJlZGRpbmckbGF5b3V0KSAlPiUKICBtdXRhdGUoU2FtcGxlcyA9IGNvbERhdGEoY2RTY0ZpbHRBbm5vdCkkU2FtcGxlKSAlPiUKICBnZ3Bsb3QoYWVzKFYxLCBWMiwgY29sb3I9U2FtcGxlcykpICsgIGdlb21fcG9pbnQoc2l6ZT0wLjc1KSArIHRoZW1lX2NsYXNzaWMoKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y19jbHVzdF9jb2wpCmBgYApIb3cgdGhlIGNsdXN0ZXJpbmcgaXMgaW1wYWN0ZWQgd2l0aCByZWFkIGNvdW50cwoKYGBge3J9CmFzLnRpYmJsZShlbWJlZGRpbmckbGF5b3V0KSAlPiUKICBtdXRhdGUoUmVhZERlcHRoID0gY2RTY0ZpbHRBbm5vdCRsb2cxMF90b3RhbF9jb3VudHMpICU+JQogIGdncGxvdChhZXMoVjEsIFYyLCBjb2xvcj1SZWFkRGVwdGgpKSArICBnZW9tX3BvaW50KHNpemU9MC43NSkgKyB0aGVtZV9jbGFzc2ljKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudG4oY29sb3VycyA9IHJhaW5ib3coNSkpCmBgYApgYGB7cn0KCmFzLnRpYmJsZShhcy5kYXRhLmZyYW1lKHRzbmVfb3V0JFkpKSAlPiUKICBtdXRhdGUoUmVhZERlcHRoID0gY2RTY0ZpbHRBbm5vdCRsb2cxMF90b3RhbF9jb3VudHMpICU+JQogIGdncGxvdChhZXMoVjEsIFYyLCBjb2xvcj1SZWFkRGVwdGgpKSArICBnZW9tX3BvaW50KHNpemU9MC43NSkgKyB0aGVtZV9jbGFzc2ljKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudG4oY29sb3VycyA9IHJhaW5ib3coNSkpCgpgYGAKCgo8IS0tVGhlIENsdXN0ZXIgcmVzdWx0IGxvb2tzIHZlcnkgZ29vZC4gQWx0aG91Z2ggdGhleSB3ZXJlIGdlbmVyYXRlZCB1c2luZyBoaXJlcmNoaWFsIGNsdXN0ZXJpbmcsIGJ1dCB3aGVuIG92ZXJsYXBwZWQgd2l0aCB0aGUgdC1TTkUgYW5kIHRoZSBVTUFQIHBsb3QgdGhleSB2ZXJ5IG5pY2VseSBvdmVybGFwcyB3aXRoIHRoZSB2aXN1YWxpemF0aW9uLiBBIGNsdXN0ZXIgb24gdGhlIGJvdHRvbSB3aGljaCBpcyBhIGJpdCBtb3JlIGNvbmZ1c2luZyBvbmUuCgpJbnRlcmVzdGluZ2x5IEhlYWx0aHkgaXMgYWx3YXlzIG1ha2luZyB0aHJlZSBjbHVzdGVycyB3aGVyZWFzIENPUEQgaXMgYWx3YXlzIG1ha2luZyB0d28gY2x1c3RlcnMuIE5lZWQgdG8gc2VlIGhvdyBtdWNoIG92ZXJsYXAgaXMgdGhlcmUuCgpUaGUgcHJvYmxlbSB3b3VsZCBiZSB0aGF0IHRoZSBzYW1wbGVzIGFyZSBzbyBkaWZmZXJudCB3ZSB3b3VsZCBub3Qga25vdyB3aGV0aGVyIHRoZSBkaWZmZXJlbmNlIGluIHRoZSBjbHVzdGVycyBhcmUgZHVlIHRvIGRpZmZlcmVudCBzYW1wbGVzIG9yIGR1ZSB0byByZWFsIGJpb2xvZ3kuLS0+CmBgYHtyfQpwMTwtYXMudGliYmxlKGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSkpICU+JQogIG11dGF0ZShSZWFkRGVwdGggPSBjZFNjRmlsdEFubm90JGxvZzEwX3RvdGFsX2NvdW50cykgJT4lCiAgZ2dwbG90KGFlcyhWMSwgVjIsIGNvbG9yPVJlYWREZXB0aCkpICsgIGdlb21fcG9pbnQoc2l6ZT0wLjc1KSArIHRoZW1lX2NsYXNzaWMoKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50bihjb2xvdXJzID0gcmFpbmJvdyg1KSkKcDI8LWFzLnRpYmJsZShhcy5kYXRhLmZyYW1lKHRzbmVfb3V0JFkpKSAlPiUKICBtdXRhdGUoQ2x1c3RlcnMgPSBjZFNjRmlsdEFubm90JENsdXN0ZXJzKSAlPiUKICBnZ3Bsb3QoYWVzKFYxLCBWMiwgY29sb3I9Q2x1c3RlcnMpKSArICBnZW9tX3BvaW50KHNpemU9MC43NSkgKyB0aGVtZV9jbGFzc2ljKCkgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNfY2x1c3RfY29sKQpgYGAKCmBgYHtyfQojcGFyKG1mcm93PWMoMSwyKSkKcDEgCnAyCmBgYAoKT25lIG9mIG15IHdvcnJ5IHdhcyB0aGF0IHRoZSBjbHVzdGVycyBhbmQgdGhlIHQtU05FIHNlcGVyYXRpb24gYXJlIGRvbWluYXRlZCBieSB0aGUgcmVhZCBkZXB0aC4gQWx0aG91Z2ggaW4gdC1TTkUgb25lIGNhbiBzZWUgdGhlIGdyYWRpZW50IG9mIHRoZSBSZWFkIERlcHRoIChyZWFkIGRlcHRocyBtb3ZpbmcgaW50byBvbmUgc2lkZSkgd2hpY2ggaGFzIGJlZW4gcmVwb3J0ZWQgaW4gdGhlIGxpdGVyYXR1cmUgW0hhZmVtZWlzdGVyIGV0LiBhbC4gMjAxOV0oZG9pOiBodHRwczovL2RvaS5vcmcvMTAuMTEwMS81NzY4MjcpIGJ1dCBpdCBzdXJlbHkgaXMgbm90IGRvbWluYXRpbmcgdGhlIGNsdXN0ZXJzIG9uIHRoZSB0LVNORSBzZXBlcmF0aW9uLiAKCgojIyBDbHVzdGVyIHZhbGlkYXRpb24KSXQgbG9va3MgbGlrZSB0aGF0IHRoZSBjbHVzdGVyaW5nIG1ldGhvZCB0aGF0IEkgcHJlZmVyIHRvIHVzZSBgZHluYW1pY1RyZWVDdXRgIGlzIHByb2R1Y2luZyAxMiBjbHVzdGVycyBhZnRlciByZW1vdmluZyB0aGUgY2VsbHMgZnJvbSAxMnRoIGNsdXN0ZXIgZnJvbSB0aGUgZmlyc3QgcnVuLiBJbiB0aGlzIG5ldyBjbHVzdGVyaW5nIGNsdXN0ZXItOCBpcyBzcHJlYWQgZXZlcnl3aGVyZSBhbmQgZG9lcyBub3Qgc2VlbSB0byBiZSBhIHZhbGlkIGNsdXN0ZXIuIFNvLCBJIHdpbGwgbm90IGFwcGx5IGNsdXN0ZXJpbmcgdmFsaWRhdGlvbiBtZXRob2RzIHRvIHNlZSB0aGlzLgoKIyMjIEludGVybmFsIG1lYXN1cmVzIGZvciBjbHVzdGVyIHZhbGlkYXRpb24KCkluIHRoaXMgc2VjdGlvbiwgd2UgZGVzY3JpYmUgdGhlIG1vc3Qgd2lkZWx5IHVzZWQgY2x1c3RlcmluZyB2YWxpZGF0aW9uIGluZGljZXMuIFJlY2FsbCB0aGF0IHRoZSBnb2FsIG9mIHBhcnRpdGlvbmluZyBjbHVzdGVyaW5nIGFsZ29yaXRobXMgKFBhcnQgQHJlZihwYXJ0aXRpb25pbmctY2x1c3RlcmluZykpIGlzIHRvIHNwbGl0IHRoZSBkYXRhIHNldCBpbnRvIGNsdXN0ZXJzIG9mIG9iamVjdHMsIHN1Y2ggdGhhdDoKCi0gdGhlIG9iamVjdHMgaW4gdGhlIHNhbWUgY2x1c3RlciBhcmUgc2ltaWxhciBhcyBtdWNoIGFzIHBvc3NpYmxlLAotIGFuZCB0aGUgb2JqZWN0cyBpbiBkaWZmZXJlbnQgY2x1c3RlcnMgYXJlIGhpZ2hseSBkaXN0aW5jdApbQ2x1c3RlciBWYWxpZGF0aW9uLTFdKGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS9wcmludC5waHA/aWQ9MjQxKQpbQ2x1c3RlciBWYWxpZGF0aW9uLTJdKGh0dHBzOi8vd3d3LmRhdGFub3ZpYS5jb20vZW4vbGVzc29ucy9jbHVzdGVyLXZhbGlkYXRpb24tc3RhdGlzdGljcy1tdXN0LWtub3ctbWV0aG9kcy8pCltDbHVzdGVyIFZhbGlkYXRpb24tM10oaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC93aWtpL3ByaW50LnBocD9pZD0yNDMpCgoKKiBUaGF0IGlzLCB3ZSB3YW50IHRoZSBhdmVyYWdlIGRpc3RhbmNlIHdpdGhpbiBjbHVzdGVyIHRvIGJlIGFzIHNtYWxsIGFzIHBvc3NpYmxlOyBhbmQgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgYmV0d2VlbiBjbHVzdGVycyB0byBiZSBhcyBsYXJnZSBhcyBwb3NzaWJsZS4KCkludGVybmFsIHZhbGlkYXRpb24gbWVhc3VyZXMgcmVmbGVjdCBvZnRlbiB0aGUgY29tcGFjdG5lc3MsIHRoZSBjb25uZWN0ZWRuZXNzIGFuZCB0aGUgc2VwYXJhdGlvbiBvZiB0aGUgY2x1c3RlciBwYXJ0aXRpb25zLgoKMS4gQ29tcGFjdG5lc3Mgb3IgY2x1c3RlciBjb2hlc2lvbjogTWVhc3VyZXMgaG93IGNsb3NlIGFyZSB0aGUgb2JqZWN0cyB3aXRoaW4gdGhlIHNhbWUgY2x1c3Rlci4gQSBsb3dlciB3aXRoaW4tY2x1c3RlciB2YXJpYXRpb24gaXMgYW4gaW5kaWNhdG9yIG9mIGEgZ29vZCBjb21wYWN0bmVzcyAoaS5lLiwgYSBnb29kIGNsdXN0ZXJpbmcpLiBUaGUgZGlmZmVyZW50IGluZGljZXMgZm9yIGV2YWx1YXRpbmcgdGhlIGNvbXBhY3RuZXNzIG9mIGNsdXN0ZXJzIGFyZSBiYXNlIG9uIGRpc3RhbmNlIG1lYXN1cmVzIHN1Y2ggYXMgdGhlIGNsdXN0ZXItd2lzZSB3aXRoaW4gYXZlcmFnZS9tZWRpYW4gZGlzdGFuY2VzIGJldHdlZW4gb2JzZXJ2YXRpb25zLgoyLiBTZXBhcmF0aW9uOiBNZWFzdXJlcyBob3cgd2VsbC1zZXBhcmF0ZWQgYSBjbHVzdGVyIGlzIGZyb20gb3RoZXIgY2x1c3RlcnMuIFRoZSBpbmRpY2VzIHVzZWQgYXMgc2VwYXJhdGlvbiBtZWFzdXJlcyBpbmNsdWRlOgogICAgKiBkaXN0YW5jZXMgYmV0d2VlbiBjbHVzdGVyIGNlbnRlcnMKICAgICogdGhlIHBhaXJ3aXNlIG1pbmltdW0gZGlzdGFuY2VzIGJldHdlZW4gb2JqZWN0cyBpbiBkaWZmZXJlbnQgY2x1c3RlcnMKMy4gQ29ubmVjdGl2aXR5OiBjb3JyZXNwb25kcyB0byB3aGF0IGV4dGVudCBpdGVtcyBhcmUgcGxhY2VkIGluIHRoZSBzYW1lIGNsdXN0ZXIgYXMgdGhlaXIgbmVhcmVzdCBuZWlnaGJvcnMgaW4gdGhlIGRhdGEgc3BhY2UuIFRoZSBjb25uZWN0aXZpdHkgaGFzIGEgdmFsdWUgYmV0d2VlbiAwIGFuZCBpbmZpbml0eSBhbmQgc2hvdWxkIGJlIG1pbmltaXplZC4KCkdlbmVyYWxseSBtb3N0IG9mIHRoZSBpbmRpY2VzIHVzZWQgZm9yIGludGVybmFsIGNsdXN0ZXJpbmcgdmFsaWRhdGlvbiBjb21iaW5lIGNvbXBhY3RuZXNzIGFuZCBzZXBhcmF0aW9uIG1lYXN1cmVzIGFzIGZvbGxvdzoKCiRJbmRleCA9IFxmcmFje1xhbHBoYSAqIFNlcGVyYXRpb259e1xiZXRhICogQ29tcGFjdG5lc3N9JAp3aGVyZSAkXGFscGhhJCBhbmQgJFxiZXRhJCBhcmUgd2VpZ2h0cy4KCiMjIyBTaWxob3VldHRlIGNvZWZmaWNpZW50CgpUaGUgc2lsaG91ZXR0ZSBhbmFseXNpcyBtZWFzdXJlcyBob3cgd2VsbCBhbiBvYnNlcnZhdGlvbiBpcyBjbHVzdGVyZWQgYW5kIGl0IGVzdGltYXRlcyB0aGUgYXZlcmFnZSBkaXN0YW5jZSBiZXR3ZWVuIGNsdXN0ZXJzLiBUaGUgc2lsaG91ZXR0ZSBwbG90IGRpc3BsYXlzIGEgbWVhc3VyZSBvZiBob3cgY2xvc2UgZWFjaCBwb2ludCBpbiBvbmUgY2x1c3RlciBpcyB0byBwb2ludHMgaW4gdGhlIG5laWdoYm9yaW5nIGNsdXN0ZXJzLgoKRm9yIGVhY2ggb2JzZXJ2YXRpb24gJGkkLCB0aGUgc2lsaG91ZXR0ZSB3aWR0aCAkc19pJCBpcyBjYWxjdWxhdGVkIGFzIGZvbGxvd3M6CgoxLiBGb3IgZWFjaCBvYnNlcnZhdGlvbiAkaSQsIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBkaXNzaW1hbGlydHkgJFxhbHBoYV9pJCBiZXR3ZWVuICRpJCBhbmQgYWxsIG90aGVyIHBvaW50cyBvZiB0aGUgY2x1c3RlciB3aGljaCAkaSQgYmVsb25ncy4KMi4gRm9yIGFsbCBvdGhlciBjbHVzdGVycyAkQyQsIHRvIHdoaWNoICRpJCBkb2VzIG5vdCBiZWxvbmcsIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBkaXNzaW1pbGFyaXR5ICRkKGksQykkIG9mICRpJCB0byBhbGwgb2JzZXJ2YXRpb25zIG9mICRDJC4gVGhlIHNtYWxsZXN0IG9mIHRoZXNlICRkKGksQykkIGlzIGRlZmluZWQgYXMgJGJfaSA9IG1pbl9DIGQoaSxDKSQuIFRoZSB2YWx1ZSBvZiAkYl9pJCBjYW4gYmUgc2VlbiBhcyB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIGkgYW5kIGl0cyBfX25laWdoYm9yX18gY2x1c3RlciwgaS5lLiB0aGUgbmVhcmVzdCBvbmUgdG8gd2hpY2ggaXQgZG9lcyBub3QgYmVsb25nLgozLiBGaW5hbGx5IHRoZSBzaWxob3VldHRlIHdpZHRoIG9mIHRoZSBvYnNlcnZhdGlvbiAkaSQgaXMgZGVmaW5lZCBieSB0aGUgZm9ybXVsYTogJFNfaSA9IFxmcmFjeyhiX2kgLSBhX2kpfXttYXgoYV9pLGJfaSl9JAoKU2lsaG91ZXR0ZSB3aWR0aCBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgZm9sbG93OgoKLSBPYnNlcnZhdGlvbnMgd2l0aCBhIGxhcmdlIFNpIChhbG1vc3QgMSkgYXJlIHZlcnkgd2VsbCBjbHVzdGVyZWQuCi0gQSBzbWFsbCBTaSAoYXJvdW5kIDApIG1lYW5zIHRoYXQgdGhlIG9ic2VydmF0aW9uIGxpZXMgYmV0d2VlbiB0d28gY2x1c3RlcnMuCi0gT2JzZXJ2YXRpb25zIHdpdGggYSBuZWdhdGl2ZSBTaSBhcmUgcHJvYmFibHkgcGxhY2VkIGluIHRoZSB3cm9uZyBjbHVzdGVyLgoKYGBge3J9CmxpYnJhcnkoY2x1c3RlcikKbGlicmFyeShmYWN0b2V4dHJhKQpgYGAKCmBgYHtyfQojbXkuY2x1c3RlcnMgPC0gdW5uYW1lKGN1dHJlZUR5bmFtaWMobXkudHJlZSwgZGlzdE09YXMubWF0cml4KG15LmRpc3QpLCB2ZXJib3NlPTApKQpmdml6X3NpbGhvdWV0dGUoc2lsaG91ZXR0ZShteS5jbHVzdGVycywgbXkuZGlzdCksIHByaW50LnN1bW1hcnkgPSBGQUxTRSkKYGBgCgoKSXQgbG9va3MgbGlrZSB0aGF0IHRoZSBzdGFiaWxpdHkgb2YgY2x1c3RlciA3LCA4ICYgMTAgaXMgcXVpdGUgbG93LiBTb21ldGhpbmcgdG8ga2VlcCBpbiBtaW5kLgoKTm93LCBob3cgZG9lcyB0aGUgYFRQNTNgIGFuZCBgWElTVGAgZXhwcmVzc2lvbiBsb29rcyBsaWtlCmBgYHtyfQpHZW5lRXhwIDwtIGxvZ2NvdW50cyhjZFNjRmlsdEFubm90KVsnWElTVCcsXQogICAgI0dlbmVOYW1lID0gJ1NQTicKICAgIGRmIDwtIGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSkKICAgIGRmWywnR2VuZUV4cCddPWxvZ2NvdW50cyhjZFNjRmlsdEFubm90KVsnWElTVCcsXQpwMTwtICAgICBnZ3Bsb3QoZGYsIGFlcyh4PVYxLCB5PVYyLCBHZW5lRXhwID0gR2VuZUV4cCkpICsKICAgICAgZ2VvbV9wb2ludChzaXplPTEuMDAsYWVzKGNvbG91ciA9IEdlbmVFeHApLCBhbHBoYT0wLjgpICsKICAgICAgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJncmF5ODgiLCBoaWdoID0gInB1cnBsZTQiKSsKICAgICAgI2d1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTQpKSkgKwogICAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgICAgZ2d0aXRsZShwYXN0ZTAoJ0dlbmUgRXhwOicsJ1hJU1QnKSkrCiAgICAgIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplPTE0KSArCiAgICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHN0cmlwLnRleHQueCAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGV4dC54ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGlja3MgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMubGluZSAgICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpCnAxCmBgYAoKYGBge3J9CkdlbmVFeHAgPC0gbG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydUUDUzJyxdCiAgICAjR2VuZU5hbWUgPSAnU1BOJwogICAgZGYgPC0gYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKQogICAgZGZbLCdHZW5lRXhwJ109bG9nY291bnRzKGNkU2NGaWx0QW5ub3QpWydUUDUzJyxdCiAgcDIgPC0gIGdncGxvdChkZiwgYWVzKHg9VjEsIHk9VjIsIEdlbmVFeHAgPSBHZW5lRXhwKSkgKwogICAgICBnZW9tX3BvaW50KHNpemU9MS4wMCxhZXMoY29sb3VyID0gR2VuZUV4cCksIGFscGhhPTAuMykgKwogICAgICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gImdyYXk4OCIsIGhpZ2ggPSAicHVycGxlNCIpKwogICAgICAjZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9NCkpKSArCiAgICAgIHhsYWIoIiIpICsgeWxhYigiIikgKwogICAgICBnZ3RpdGxlKHBhc3RlMCgnR2VuZSBFeHA6JywnVFA1MycpKSsKICAgICAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTQpICsKICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50ZXh0LnkgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy5saW5lICAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgcGFuZWwuYm9yZGVyICAgICA9IGVsZW1lbnRfYmxhbmsoKSkKICBwMgpgYGAKCmBgYHtyfQpwMyA8LSBnZ3Bsb3QoYXMuZGF0YS5mcmFtZSh0c25lX291dCRZKSwgYWVzKHg9VjEsIHk9VjIsIGNvbG9yPVJlcCkpICsKICAgICBnZW9tX3BvaW50KHNpemU9MC43NSkgKwogICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTAuOCkpKSArCiAgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgICAgZ2d0aXRsZSgidC1TTkUgMkQgRW1iZWRkaW5nIG9mIEV4cHJlc3Npb24gRGF0YSIpICsKICAgICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xMCkgKwogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jX3NhbXBsZV9jb2wpICsKICAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y19zYW1wbGVfY29sKQpwMwpgYGAKCmBgYHtyfQpwNDwtYXMudGliYmxlKGFzLmRhdGEuZnJhbWUodHNuZV9vdXQkWSkpICU+JQogIG11dGF0ZShDbHVzdGVycyA9IGNkU2NGaWx0QW5ub3QkQ2x1c3RlcnMpICU+JQogIGdncGxvdChhZXMoeD1WMSwgeT1WMiwgY29sb3I9Q2x1c3RlcnMpKSArICAKICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0wLjgpKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0wLjc1KSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgc3RyaXAudGV4dC54ICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLnRleHQueCAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIGF4aXMudGV4dC55ICAgICAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICBheGlzLmxpbmUgICAgICAgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y19jbHVzdF9jb2wpCnA0CmBgYAoKYGBge3IgbXVsdFBsb3RXaXRoWElTVF9UUDUzLCBmaWcuaGVpZ2h0PTh9Cm11bHRpcGxvdChwMSxwMyxwMixwNCxjb2xzPTIpCmBgYAoKCiMgSWRlbnRpZnlpbmcgbWFya2VyIGdlbmVzCgpQb3RlbnRpYWwgbWFya2VyIGdlbmVzIGFyZSBpZGVudGlmaWVkIGJ5IHRha2luZyB0aGUgdG9wIHNldCBvZiBERSBnZW5lcyBmcm9tIGVhY2ggcGFpcndpc2UgY29tcGFyaXNvbiBiZXR3ZWVuIGNsdXN0ZXJzLiBXZSBhcnJhbmdlIHRoZSByZXN1bHRzIGludG8gYSBzaW5nbGUgb3V0cHV0IHRhYmxlIHRoYXQgYWxsb3dzIGEgbWFya2VyIHNldCB0byBiZSBlYXNpbHkgZGVmaW5lZCBmb3IgYSB1c2VyLXNwZWNpZmllZCBzaXplIG9mIHRoZSB0b3Agc2V0LiBGb3IgZXhhbXBsZSwgdG8gY29uc3RydWN0IGEgbWFya2VyIHNldCBmcm9tIHRoZSB0b3AgMTAgZ2VuZXMgb2YgZWFjaCBjb21wYXJpc29uLCBvbmUgd291bGQgZmlsdGVyIGBtYXJrZXIuc2V0YCB0byByZXRhaW4gcm93cyB3aXRoIFRvcCBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gMTAuCgpXZSBzYXZlIHRoZSBsaXN0IG9mIGNhbmRpZGF0ZSBtYXJrZXIgZ2VuZXMgZm9yIGZ1cnRoZXIgZXhhbWluYXRpb24uIFdlIGFsc28gZXhhbWluZSB0aGVpciBleHByZXNzaW9uIHByb2ZpbGVzIHRvIHZlcmlmeSB0aGF0IHRoZSBERSBzaWduYXR1cmUgaXMgcm9idXN0LiBUaGUgaGVhdG1hcCBmaWd1cmUgYmVsb3cgaW5kaWNhdGVzIHRoYXQgbW9zdCBvZiB0aGUgdG9wIG1hcmtlcnMgaGF2ZSBzdHJvbmcgYW5kIGNvbnNpc3RlbnQgdXAtIG9yIGRvd25yZWd1bGF0aW9uIGluIGNlbGxzIG9mIGNsdXN0ZXIgMSBjb21wYXJlZCB0byBzb21lIG9yIGFsbCBvZiB0aGUgb3RoZXIgY2x1c3RlcnMuIFRodXMsIGNlbGxzIGZyb20gdGhlIHN1YnBvcHVsYXRpb24gb2YgaW50ZXJlc3QgY2FuIGJlIGlkZW50aWZpZWQgYXMgdGhvc2UgdGhhdCBleHByZXNzIHRoZSB1cHJlZ3VsYXRlZCBtYXJrZXJzIGFuZCBkbyBub3QgZXhwcmVzcyB0aGUgZG93bnJlZ3VsYXRlZCBtYXJrZXJzLgoKYGBge3J9CmxpYnJhcnkoZWRnZVIpCmBgYAoKCmBgYHtyfQpjbHVzdGVyIDwtIGZhY3RvcihteS5jbHVzdGVycykKZGUuZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+MCArIGNsdXN0ZXIpCnkgPC0gY29udmVydFRvKGNkU2NGaWx0QW5ub3QsIHR5cGU9ImVkZ2VSIikKeSA8LSBlc3RpbWF0ZURpc3AoeSwgZGUuZGVzaWduKQpmaXQgPC0gZ2xtRml0KHksIGRlLmRlc2lnbikKc3VtbWFyeSh5JHRhZ3dpc2UuZGlzcGVyc2lvbikKYGBgCmBgYHtyfQpjbHVzdC5jb2wgPC0gcmFpbmJvdyhtYXgobXkuY2x1c3RlcnMpKQpgYGAKCiMjIENsdXN0ZXIxIG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0MS5sb2dGQyA8LSByZXN1bHQxLkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjEiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0MS5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0MS5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXIxLnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDEubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDEuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXIxLnNldCA8LSBtYXJrZXIxLnNldFtvcmRlcihtYXJrZXIxLnNldCRUb3ApLF0KCm1hcmtlcjEuc2V0LnBvcyA8LSBtYXJrZXIxLnNldFtyb3dTdW1zKG1hcmtlcjEuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjEuc2V0LnBvcyA8LSBtYXJrZXIxLnNldC5wb3Nbb3JkZXIobWFya2VyMS5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXIxLnNldCwgMTApCmBgYApgYGB7cn0Kd3JpdGUudGFibGUobWFya2VyMS5zZXQsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyMS50c3YiLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKd3JpdGUudGFibGUobWFya2VyMS5zZXQucG9zLCBmaWxlPSJMb3Vpc2FfTmVsc29uXzEwWERhdGFzZXRfQ2x1c3RlcjFfcG9zLnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp0b3AubWFya2VycyA8LSBtYXJrZXIxLnNldCRHZW5lW21hcmtlcjEuc2V0JFRvcCA8PSAxMF0KYGBgCgoKIyMgQ2x1c3RlcjIgbWFya2VyIGdlbmVzCmBgYHtyfQpyZXN1bHQyLmxvZ0ZDIDwtIHJlc3VsdDIuRkRSIDwtIGxpc3QoKQpjaG9zZW4uY2x1c3QgPC0gd2hpY2gobGV2ZWxzKGNsdXN0ZXIpPT0iMiIpICMgY2hhcmFjdGVyLCBhcyDigJljbHVzdGVy4oCZIGlzIGEgZmFjdG9yLgpmb3IgKGNsdXN0IGluIHNlcV9sZW4obmxldmVscyhjbHVzdGVyKSkpIHsKICAgIGlmIChjbHVzdD09Y2hvc2VuLmNsdXN0KSB7IG5leHQgfQogICAgY29udHJhc3QgPC0gbnVtZXJpYyhuY29sKGRlLmRlc2lnbikpCiAgICBjb250cmFzdFtjaG9zZW4uY2x1c3RdIDwtIDEKICAgIGNvbnRyYXN0W2NsdXN0XSA8LSAtMQogICAgZml0IDwtIGdsbVFMRml0KHksIGRlLmRlc2lnbikKICAgIHJlcyA8LSBnbG1RTEZUZXN0KGZpdCwgY29udHJhc3QgPSBjb250cmFzdCkKICAgIHRvcC50YWdzIDwtIHRvcFRhZ3MocmVzLCBuPWxlbmd0aCh5JGRlc2lnbiksIHNvcnQuYnk9Im5vbmUiKQogICAgCiAgICBjb24ubmFtZSA8LSBwYXN0ZTAoJ3ZzLicsIGxldmVscyhjbHVzdGVyKVtjbHVzdF0pCiAgICByZXN1bHQyLmxvZ0ZDW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJGxvZ0ZDCiAgICAjbmFtZXMocmVzdWx0MS5sb2dGQ1tbY29uLm5hbWVdXSkgPC0gcm93bmFtZXModG9wLnRhZ3MkdGFibGUpCiAgICByZXN1bHQyLkZEUltbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRGRFIKICAgICNuYW1lcyhyZXN1bHQxLkZEUltbY29uLm5hbWVdXSkgPC0gcm93bmFtZXModG9wLnRhZ3MkdGFibGUpCn0KYGBgCgpgYGB7cn0KY29sbGVjdGVkLnJhbmtzIDwtIGxhcHBseShyZXN1bHQyLkZEUiwgcmFuaywgdGllcz0iZmlyc3QiKQptaW4ucmFuayA8LSBkby5jYWxsKHBtaW4sIGNvbGxlY3RlZC5yYW5rcykKCm1hcmtlcjIuc2V0IDwtIGRhdGEuZnJhbWUoVG9wPW1pbi5yYW5rLCBHZW5lPXJvd25hbWVzKHkpLAogICAgbG9nRkM9ZG8uY2FsbChjYmluZCwgcmVzdWx0Mi5sb2dGQyksIAogICAgRkRSID0gcmVzdWx0Mi5GRFIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCm1hcmtlcjIuc2V0IDwtIG1hcmtlcjIuc2V0W29yZGVyKG1hcmtlcjIuc2V0JFRvcCksXQoKbWFya2VyMi5zZXQucG9zIDwtIG1hcmtlcjIuc2V0W3Jvd1N1bXMobWFya2VyMi5zZXRbLDM6MTFdPjApPT05LF0KbWFya2VyMi5zZXQucG9zIDwtIG1hcmtlcjIuc2V0LnBvc1tvcmRlcihtYXJrZXIyLnNldC5wb3MkVG9wKSxdCgpoZWFkKG1hcmtlcjIuc2V0LCAxMCkKYGBgCgpgYGB7cn0KbWFya2VyMi5zZXQuc3Ryb25nLmdlbmUgPC0gIG1hcmtlcjIuc2V0W3Jvd1N1bXMoYWJzKG1hcmtlcjIuc2V0WywzOjEzXSk+MSk+NixdCmhlYWQobWFya2VyMi5zZXQuc3Ryb25nLmdlbmUpCmBgYApgYGB7cn0Kd3JpdGUudGFibGUobWFya2VyMi5zZXQuc3Ryb25nLmdlbmUsIGZpbGU9IkNsdXN0ZXIyX0hpZ2hMbzJGb2xkLnR4diIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQpgYGAKCgoKYGBge3J9CndyaXRlLnRhYmxlKG1hcmtlcjIuc2V0LCBmaWxlPSJMb3Vpc2FfTmVsc29uXzEwWERhdGFzZXRfQ2x1c3RlcjIudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCndyaXRlLnRhYmxlKG1hcmtlcjIuc2V0LnBvcywgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXIyX3Bvcy50c3YiLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKdG9wLm1hcmtlcnMgPC0gbWFya2VyMi5zZXQkR2VuZVttYXJrZXIyLnNldCRUb3AgPD0gMTBdCmBgYAoKCiMjIENsdXN0ZXIzIG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0My5sb2dGQyA8LSByZXN1bHQzLkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjMiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0My5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0My5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0My5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXIzLnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDMubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDMuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXIzLnNldCA8LSBtYXJrZXIzLnNldFtvcmRlcihtYXJrZXIzLnNldCRUb3ApLF0KCm1hcmtlcjMuc2V0LnBvcyA8LSBtYXJrZXIzLnNldFtyb3dTdW1zKG1hcmtlcjMuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjMuc2V0LnBvcyA8LSBtYXJrZXIzLnNldC5wb3Nbb3JkZXIobWFya2VyMy5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXIzLnNldCwgMTApCmBgYAoKCmBgYHtyfQp3cml0ZS50YWJsZShtYXJrZXIzLnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXIzLnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp3cml0ZS50YWJsZShtYXJrZXIzLnNldC5wb3MsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyM19wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjMuc2V0JEdlbmVbbWFya2VyMS5zZXQkVG9wIDw9IDEwXQpgYGAKCiMjIENsdXN0ZXI0IG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0NC5sb2dGQyA8LSByZXN1bHQ0LkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjQiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0NC5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0NC5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0NC5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXI0LnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDQubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDQuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXI0LnNldCA8LSBtYXJrZXI0LnNldFtvcmRlcihtYXJrZXI0LnNldCRUb3ApLF0KCm1hcmtlcjQuc2V0LnBvcyA8LSBtYXJrZXI0LnNldFtyb3dTdW1zKG1hcmtlcjQuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjQuc2V0LnBvcyA8LSBtYXJrZXI0LnNldC5wb3Nbb3JkZXIobWFya2VyNC5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXI0LnNldCwgMTApCmBgYAoKCmBgYHtyfQp3cml0ZS50YWJsZShtYXJrZXI0LnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXI0LnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp3cml0ZS50YWJsZShtYXJrZXI0LnNldC5wb3MsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyNF9wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjEuc2V0JEdlbmVbbWFya2VyNC5zZXQkVG9wIDw9IDEwXQpgYGAKCiMjIENsdXN0ZXI1IG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0NS5sb2dGQyA8LSByZXN1bHQ1LkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjUiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0NS5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0NS5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0NS5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXI1LnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDUubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDUuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXI1LnNldCA8LSBtYXJrZXI1LnNldFtvcmRlcihtYXJrZXI1LnNldCRUb3ApLF0KCm1hcmtlcjUuc2V0LnBvcyA8LSBtYXJrZXI1LnNldFtyb3dTdW1zKG1hcmtlcjUuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjUuc2V0LnBvcyA8LSBtYXJrZXI1LnNldC5wb3Nbb3JkZXIobWFya2VyNS5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXI1LnNldCwgMTApCmBgYAoKCgoKYGBge3J9CiN3cml0ZS50YWJsZShtYXJrZXI1LnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXI1LnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp3cml0ZS50YWJsZShtYXJrZXI1LnNldC5wb3MsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyNV9wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjUuc2V0JEdlbmVbbWFya2VyNS5zZXQkVG9wIDw9IDEwXQpgYGAKCiMjIENsdXN0ZXI2IG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0Ni5sb2dGQyA8LSByZXN1bHQ2LkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjYiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0Ni5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0Ni5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0Ni5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXI2LnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDYubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDYuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXI2LnNldCA8LSBtYXJrZXI2LnNldFtvcmRlcihtYXJrZXI2LnNldCRUb3ApLF0KCm1hcmtlcjYuc2V0LnBvcyA8LSBtYXJrZXI2LnNldFtyb3dTdW1zKG1hcmtlcjYuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjYuc2V0LnBvcyA8LSBtYXJrZXI2LnNldC5wb3Nbb3JkZXIobWFya2VyNi5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXI2LnNldCwgMTApCmBgYAoKCmBgYHtyfQp3cml0ZS50YWJsZShtYXJrZXI2LnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXI2LnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp3cml0ZS50YWJsZShtYXJrZXI2LnNldC5wb3MsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyNl9wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjYuc2V0JEdlbmVbbWFya2VyMS5zZXQkVG9wIDw9IDEwXQpgYGAKCgojIyBDbHVzdGVyNyBtYXJrZXIgZ2VuZXMKYGBge3J9CnJlc3VsdDcubG9nRkMgPC0gcmVzdWx0Ny5GRFIgPC0gbGlzdCgpCmNob3Nlbi5jbHVzdCA8LSB3aGljaChsZXZlbHMoY2x1c3Rlcik9PSI3IikgIyBjaGFyYWN0ZXIsIGFzIOKAmWNsdXN0ZXLigJkgaXMgYSBmYWN0b3IuCmZvciAoY2x1c3QgaW4gc2VxX2xlbihubGV2ZWxzKGNsdXN0ZXIpKSkgewogICAgaWYgKGNsdXN0PT1jaG9zZW4uY2x1c3QpIHsgbmV4dCB9CiAgICBjb250cmFzdCA8LSBudW1lcmljKG5jb2woZGUuZGVzaWduKSkKICAgIGNvbnRyYXN0W2Nob3Nlbi5jbHVzdF0gPC0gMQogICAgY29udHJhc3RbY2x1c3RdIDwtIC0xCiAgICBmaXQgPC0gZ2xtUUxGaXQoeSwgZGUuZGVzaWduKQogICAgcmVzIDwtIGdsbVFMRlRlc3QoZml0LCBjb250cmFzdCA9IGNvbnRyYXN0KQogICAgdG9wLnRhZ3MgPC0gdG9wVGFncyhyZXMsIG49bGVuZ3RoKHkkZGVzaWduKSwgc29ydC5ieT0ibm9uZSIpCiAgICAKICAgIGNvbi5uYW1lIDwtIHBhc3RlMCgndnMuJywgbGV2ZWxzKGNsdXN0ZXIpW2NsdXN0XSkKICAgIHJlc3VsdDcubG9nRkNbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkbG9nRkMKICAgICNuYW1lcyhyZXN1bHQxLmxvZ0ZDW1tjb24ubmFtZV1dKSA8LSByb3duYW1lcyh0b3AudGFncyR0YWJsZSkKICAgIHJlc3VsdDcuRkRSW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJEZEUgogICAgI25hbWVzKHJlc3VsdDEuRkRSW1tjb24ubmFtZV1dKSA8LSByb3duYW1lcyh0b3AudGFncyR0YWJsZSkKfQpgYGAKCmBgYHtyfQpjb2xsZWN0ZWQucmFua3MgPC0gbGFwcGx5KHJlc3VsdDcuRkRSLCByYW5rLCB0aWVzPSJmaXJzdCIpCm1pbi5yYW5rIDwtIGRvLmNhbGwocG1pbiwgY29sbGVjdGVkLnJhbmtzKQoKbWFya2VyNy5zZXQgPC0gZGF0YS5mcmFtZShUb3A9bWluLnJhbmssIEdlbmU9cm93bmFtZXMoeSksCiAgICBsb2dGQz1kby5jYWxsKGNiaW5kLCByZXN1bHQ3LmxvZ0ZDKSwgCiAgICBGRFIgPSByZXN1bHQ3LkZEUiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKbWFya2VyNy5zZXQgPC0gbWFya2VyNy5zZXRbb3JkZXIobWFya2VyNy5zZXQkVG9wKSxdCgptYXJrZXI3LnNldC5wb3MgPC0gbWFya2VyNy5zZXRbcm93U3VtcyhtYXJrZXI3LnNldFssMzoxMV0+MCk9PTksXQptYXJrZXI3LnNldC5wb3MgPC0gbWFya2VyNy5zZXQucG9zW29yZGVyKG1hcmtlcjcuc2V0LnBvcyRUb3ApLF0KCmhlYWQobWFya2VyNy5zZXQsIDEwKQpgYGAKCgpgYGB7cn0Kd3JpdGUudGFibGUobWFya2VyNy5zZXQsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyNy50c3YiLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKd3JpdGUudGFibGUobWFya2VyNy5zZXQucG9zLCBmaWxlPSJMb3Vpc2FfTmVsc29uXzEwWERhdGFzZXRfQ2x1c3RlcjdfcG9zLnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp0b3AubWFya2VycyA8LSBtYXJrZXI3LnNldCRHZW5lW21hcmtlcjcuc2V0JFRvcCA8PSAxMF0KYGBgCgojIyBDbHVzdGVyOCBtYXJrZXIgZ2VuZXMKYGBge3J9CnJlc3VsdDgubG9nRkMgPC0gcmVzdWx0OC5GRFIgPC0gbGlzdCgpCmNob3Nlbi5jbHVzdCA8LSB3aGljaChsZXZlbHMoY2x1c3Rlcik9PSI4IikgIyBjaGFyYWN0ZXIsIGFzIOKAmWNsdXN0ZXLigJkgaXMgYSBmYWN0b3IuCmZvciAoY2x1c3QgaW4gc2VxX2xlbihubGV2ZWxzKGNsdXN0ZXIpKSkgewogICAgaWYgKGNsdXN0PT1jaG9zZW4uY2x1c3QpIHsgbmV4dCB9CiAgICBjb250cmFzdCA8LSBudW1lcmljKG5jb2woZGUuZGVzaWduKSkKICAgIGNvbnRyYXN0W2Nob3Nlbi5jbHVzdF0gPC0gMQogICAgY29udHJhc3RbY2x1c3RdIDwtIC0xCiAgICBmaXQgPC0gZ2xtUUxGaXQoeSwgZGUuZGVzaWduKQogICAgcmVzIDwtIGdsbVFMRlRlc3QoZml0LCBjb250cmFzdCA9IGNvbnRyYXN0KQogICAgdG9wLnRhZ3MgPC0gdG9wVGFncyhyZXMsIG49bGVuZ3RoKHkkZGVzaWduKSwgc29ydC5ieT0ibm9uZSIpCiAgICAKICAgIGNvbi5uYW1lIDwtIHBhc3RlMCgndnMuJywgbGV2ZWxzKGNsdXN0ZXIpW2NsdXN0XSkKICAgIHJlc3VsdDgubG9nRkNbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkbG9nRkMKICAgICNuYW1lcyhyZXN1bHQxLmxvZ0ZDW1tjb24ubmFtZV1dKSA8LSByb3duYW1lcyh0b3AudGFncyR0YWJsZSkKICAgIHJlc3VsdDguRkRSW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJEZEUgogICAgI25hbWVzKHJlc3VsdDEuRkRSW1tjb24ubmFtZV1dKSA8LSByb3duYW1lcyh0b3AudGFncyR0YWJsZSkKfQpgYGAKCmBgYHtyfQpjb2xsZWN0ZWQucmFua3MgPC0gbGFwcGx5KHJlc3VsdDguRkRSLCByYW5rLCB0aWVzPSJmaXJzdCIpCm1pbi5yYW5rIDwtIGRvLmNhbGwocG1pbiwgY29sbGVjdGVkLnJhbmtzKQoKbWFya2VyOC5zZXQgPC0gZGF0YS5mcmFtZShUb3A9bWluLnJhbmssIEdlbmU9cm93bmFtZXMoeSksCiAgICBsb2dGQz1kby5jYWxsKGNiaW5kLCByZXN1bHQ4LmxvZ0ZDKSwgCiAgICBGRFIgPSByZXN1bHQ4LkZEUiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKbWFya2VyOC5zZXQgPC0gbWFya2VyOC5zZXRbb3JkZXIobWFya2VyOC5zZXQkVG9wKSxdCgptYXJrZXI4LnNldC5wb3MgPC0gbWFya2VyOC5zZXRbcm93U3VtcyhtYXJrZXI4LnNldFssMzoxMV0+MCk9PTksXQptYXJrZXI4LnNldC5wb3MgPC0gbWFya2VyOC5zZXQucG9zW29yZGVyKG1hcmtlcjguc2V0LnBvcyRUb3ApLF0KCmhlYWQobWFya2VyOC5zZXQsIDEwKQpgYGAKCgpgYGB7cn0Kd3JpdGUudGFibGUobWFya2VyOC5zZXQsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyOC50c3YiLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKd3JpdGUudGFibGUobWFya2VyOC5zZXQucG9zLCBmaWxlPSJMb3Vpc2FfTmVsc29uXzEwWERhdGFzZXRfQ2x1c3RlcjhfcG9zLnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp0b3AubWFya2VycyA8LSBtYXJrZXI4LnNldCRHZW5lW21hcmtlcjguc2V0JFRvcCA8PSAxMF0KYGBgCgoKCiMjIENsdXN0ZXI5IG1hcmtlciBnZW5lcwpgYGB7cn0KcmVzdWx0OS5sb2dGQyA8LSByZXN1bHQ5LkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjkiKSAjIGNoYXJhY3RlciwgYXMg4oCZY2x1c3RlcuKAmSBpcyBhIGZhY3Rvci4KZm9yIChjbHVzdCBpbiBzZXFfbGVuKG5sZXZlbHMoY2x1c3RlcikpKSB7CiAgICBpZiAoY2x1c3Q9PWNob3Nlbi5jbHVzdCkgeyBuZXh0IH0KICAgIGNvbnRyYXN0IDwtIG51bWVyaWMobmNvbChkZS5kZXNpZ24pKQogICAgY29udHJhc3RbY2hvc2VuLmNsdXN0XSA8LSAxCiAgICBjb250cmFzdFtjbHVzdF0gPC0gLTEKICAgIGZpdCA8LSBnbG1RTEZpdCh5LCBkZS5kZXNpZ24pCiAgICByZXMgPC0gZ2xtUUxGVGVzdChmaXQsIGNvbnRyYXN0ID0gY29udHJhc3QpCiAgICB0b3AudGFncyA8LSB0b3BUYWdzKHJlcywgbj1sZW5ndGgoeSRkZXNpZ24pLCBzb3J0LmJ5PSJub25lIikKICAgIAogICAgY29uLm5hbWUgPC0gcGFzdGUwKCd2cy4nLCBsZXZlbHMoY2x1c3RlcilbY2x1c3RdKQogICAgcmVzdWx0OS5sb2dGQ1tbY29uLm5hbWVdXSA8LSB0b3AudGFncyR0YWJsZSRsb2dGQwogICAgI25hbWVzKHJlc3VsdDEubG9nRkNbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQogICAgcmVzdWx0OS5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0OS5GRFIsIHJhbmssIHRpZXM9ImZpcnN0IikKbWluLnJhbmsgPC0gZG8uY2FsbChwbWluLCBjb2xsZWN0ZWQucmFua3MpCgptYXJrZXI5LnNldCA8LSBkYXRhLmZyYW1lKFRvcD1taW4ucmFuaywgR2VuZT1yb3duYW1lcyh5KSwKICAgIGxvZ0ZDPWRvLmNhbGwoY2JpbmQsIHJlc3VsdDkubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDkuRkRSLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQptYXJrZXI5LnNldCA8LSBtYXJrZXI5LnNldFtvcmRlcihtYXJrZXI5LnNldCRUb3ApLF0KCm1hcmtlcjkuc2V0LnBvcyA8LSBtYXJrZXI5LnNldFtyb3dTdW1zKG1hcmtlcjkuc2V0WywzOjExXT4wKT09OSxdCm1hcmtlcjkuc2V0LnBvcyA8LSBtYXJrZXI5LnNldC5wb3Nbb3JkZXIobWFya2VyOS5zZXQucG9zJFRvcCksXQoKaGVhZChtYXJrZXI5LnNldCwgMTApCmBgYAoKCmBgYHtyfQp3cml0ZS50YWJsZShtYXJrZXI5LnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXI5LnRzdiIsIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgY29sLm5hbWVzPU5BKQp3cml0ZS50YWJsZShtYXJrZXI5LnNldC5wb3MsIGZpbGU9IkxvdWlzYV9OZWxzb25fMTBYRGF0YXNldF9DbHVzdGVyOV9wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjkuc2V0JEdlbmVbbWFya2VyMS5zZXQkVG9wIDw9IDEwXQpgYGAKCiMjIENsdXN0ZXIxMCBtYXJrZXIgZ2VuZXMKYGBge3J9CnJlc3VsdDEwLmxvZ0ZDIDwtIHJlc3VsdDEwLkZEUiA8LSBsaXN0KCkKY2hvc2VuLmNsdXN0IDwtIHdoaWNoKGxldmVscyhjbHVzdGVyKT09IjEwIikgIyBjaGFyYWN0ZXIsIGFzIOKAmWNsdXN0ZXLigJkgaXMgYSBmYWN0b3IuCmZvciAoY2x1c3QgaW4gc2VxX2xlbihubGV2ZWxzKGNsdXN0ZXIpKSkgewogICAgaWYgKGNsdXN0PT1jaG9zZW4uY2x1c3QpIHsgbmV4dCB9CiAgICBjb250cmFzdCA8LSBudW1lcmljKG5jb2woZGUuZGVzaWduKSkKICAgIGNvbnRyYXN0W2Nob3Nlbi5jbHVzdF0gPC0gMQogICAgY29udHJhc3RbY2x1c3RdIDwtIC0xCiAgICBmaXQgPC0gZ2xtUUxGaXQoeSwgZGUuZGVzaWduKQogICAgcmVzIDwtIGdsbVFMRlRlc3QoZml0LCBjb250cmFzdCA9IGNvbnRyYXN0KQogICAgdG9wLnRhZ3MgPC0gdG9wVGFncyhyZXMsIG49bGVuZ3RoKHkkZGVzaWduKSwgc29ydC5ieT0ibm9uZSIpCiAgICAKICAgIGNvbi5uYW1lIDwtIHBhc3RlMCgndnMuJywgbGV2ZWxzKGNsdXN0ZXIpW2NsdXN0XSkKICAgIHJlc3VsdDEwLmxvZ0ZDW1tjb24ubmFtZV1dIDwtIHRvcC50YWdzJHRhYmxlJGxvZ0ZDCiAgICAjbmFtZXMocmVzdWx0MS5sb2dGQ1tbY29uLm5hbWVdXSkgPC0gcm93bmFtZXModG9wLnRhZ3MkdGFibGUpCiAgICByZXN1bHQxMC5GRFJbW2Nvbi5uYW1lXV0gPC0gdG9wLnRhZ3MkdGFibGUkRkRSCiAgICAjbmFtZXMocmVzdWx0MS5GRFJbW2Nvbi5uYW1lXV0pIDwtIHJvd25hbWVzKHRvcC50YWdzJHRhYmxlKQp9CmBgYAoKYGBge3J9CmNvbGxlY3RlZC5yYW5rcyA8LSBsYXBwbHkocmVzdWx0MTAuRkRSLCByYW5rLCB0aWVzPSJmaXJzdCIpCm1pbi5yYW5rIDwtIGRvLmNhbGwocG1pbiwgY29sbGVjdGVkLnJhbmtzKQoKbWFya2VyMTAuc2V0IDwtIGRhdGEuZnJhbWUoVG9wPW1pbi5yYW5rLCBHZW5lPXJvd25hbWVzKHkpLAogICAgbG9nRkM9ZG8uY2FsbChjYmluZCwgcmVzdWx0MTAubG9nRkMpLCAKICAgIEZEUiA9IHJlc3VsdDEwLkZEUiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKbWFya2VyMTAuc2V0IDwtIG1hcmtlcjEwLnNldFtvcmRlcihtYXJrZXIxMC5zZXQkVG9wKSxdCgptYXJrZXIxMC5zZXQucG9zIDwtIG1hcmtlcjEwLnNldFtyb3dTdW1zKG1hcmtlcjEwLnNldFssMzoxMV0+MCk9PTksXQptYXJrZXIxMC5zZXQucG9zIDwtIG1hcmtlcjEwLnNldC5wb3Nbb3JkZXIobWFya2VyMTAuc2V0LnBvcyRUb3ApLF0KCmhlYWQobWFya2VyMTAuc2V0LCAxMCkKYGBgCgoKYGBge3J9CndyaXRlLnRhYmxlKG1hcmtlcjEwLnNldCwgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXIxMC50c3YiLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIGNvbC5uYW1lcz1OQSkKd3JpdGUudGFibGUobWFya2VyMTAuc2V0LnBvcywgZmlsZT0iTG91aXNhX05lbHNvbl8xMFhEYXRhc2V0X0NsdXN0ZXIxMF9wb3MudHN2Iiwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCBjb2wubmFtZXM9TkEpCnRvcC5tYXJrZXJzIDwtIG1hcmtlcjEwLnNldCRHZW5lW21hcmtlcjEwLnNldCRUb3AgPD0gMTBdCmBgYAoK